Don't Repeat Yourself

Don't Repeat Yourself (DRY) is a principle of software development aimed at reducing repetition of all kinds. -- wikipedia

My Rust 2021

It will be 3 years since I started to code in Rust. I had started to code 6 years ago, so half of my career have been dedicated to write code in Rust. Over recent years, I organised a Rust conference happened in Japan, and I'm now organising RustFest Global with amazing organisers (big thank you to Flaki, Jan-Erik, and other warm people. We're now working hard, stay tuned on Nov 7th-8th!).

I decided to jot down about Rust 2021. My expectations for the future of Rust are the following:

  1. Support async function in trait (and impl).
  2. Describe the difference between tokio and async-std more clear.
  3. Work on more materials for intermediate / advanced Rustaceans.

Support async function in trait (and impl)

Refs to Support `async fn` in trait methods (async/await follow-up) · Issue #2739 · rust-lang/rfcs · GitHub.

Now the Rust users try to implement the following code, we should use async-trait crate. For instance, the following one will be compile error.

trait AsyncTrait {
    async fn f() {
        println!("Couldn't compile");
    }
}

Then we will get an error:

async-trait-sandbox is 📦 v0.1.0 via 🦀 v1.44.0 on ☁️  ap-northeast-1
❯ cargo check
    Checking async-trait-sandbox v0.1.0
error[E0706]: functions in traits cannot be declared `async`
  --> src/main.rs:8:5
   |
8  |       async fn f() {
   |       ^----
   |       |
   |  _____`async` because of this
   | |
9  | |         println!("Couldn't compile");
10 | |     }
   | |_____^
   |
   = note: `async` trait functions are not currently supported
   = note: consider using the `async-trait` crate: https://crates.io/crates/async-trait

To avoid this error for a meantime, we should add a crate async-trait. Add the following line to Cargo.toml.

[dependencies]
async-trait = "0.1.36"

Add #[async_trait] attribute then we will be able to pass the strict compiler :)

use async_trait::async_trait;

#[async_trait]
trait AsyncTrait {
    async fn f() {
        println!("Could compile");
    }
}

async-trait is an awesome crate that enable us to async code being more flexible. However, I want Rust to support async fn in trait.

In developing web applications, we're used to build them with Dependency Injection (DI). When we're to use DI, interfaces and implementations are decoupled by using trait. For instance, I'm usually architecting components that fetch data from persistence layers as bellow:

#[async_trait]
pub trait TodoRepository {
    fn get(&self, id: String) -> Result<Option<Todo>, AppError>;
    fn insert(&mut self, todo: Todo) -> Result<Option<Todo>, AppError>;
    fn update(&mut self, todo: Todo) -> Result<Option<Todo>, AppError>;
    fn delete(&mut self, id: String) -> Result<(), AppError>;
}

#[async_trait]
impl TodoRepository for TodoRepositoryImpl {
    fn get(&self, id: String) -> Result<Option<Todo>, AppError> {
        // implementation
    }

    fn insert(&mut self, todo: Todo) -> Result<Option<Todo>, AppError> {
        // implementation
    }

    fn update(&mut self, todo: Todo) -> Result<Option<Todo>, AppError> {
        // implementation
    }

    fn delete(&mut self, id: String) -> Result<(), AppError> {
        // implementation
    }
}

The #[async_trait] attribute needs to set both on trait and impl but that's a bit noisy. I wanna define async fn directly as below.

pub trait TodoRepository {
    async fn get(&self, id: String) -> Result<Option<Todo>, AppError>;
    async fn insert(&mut self, todo: Todo) -> Result<Option<Todo>, AppError>;
    async fn update(&mut self, todo: Todo) -> Result<Option<Todo>, AppError>;
    async fn delete(&mut self, id: String) -> Result<(), AppError>;
}

impl TodoRepository for TodoRepositoryImpl {
    async fn get(&self, id: String) -> Result<Option<Todo>, AppError> {
        // implementation
    }

    async fn insert(&mut self, todo: Todo) -> Result<Option<Todo>, AppError> {
        // implementation
    }

    async fn update(&mut self, todo: Todo) -> Result<Option<Todo>, AppError> {
        // implementation
    }

    async fn delete(&mut self, id: String) -> Result<(), AppError> {
        // implementation
    }
}

I feel it more natural in case I use async fn than if use the handy crate.

Describe the difference between tokio and async-std more clear

Every time I introduce Rust to my coworkers (they're web developers as well), I always feel tough to describe the difference between tokio and async-std because there seems to be no clear description or documentation regarding the difference of two runtimes. I can find related posts on Reddit, on the other hand can't find official statements.

Work on more materials for intermediate / advanced Rustaceans

Move on to the next topic. The next one is about Rust books and guides.

Now we have a lot of introduction guides thanks to the community and enthusiastic users but mostly for beginners. We have few kinda advanced guide we can find out the same in other languages as "Effective XXX".

As mentioned in the Rust survey 2019 results, those who have finished "The Rust Programming Language" and used Rust for a long time seem to hope to read advanced material.

The topic will be about (these are a homage to "Effective Python" lol):

  • Rustic Thinking (how to design Rust application with trait and struct, tackling on clone(), hacking with smart pointers, and so on)
  • Functions (introduce some useful functions)
  • Testing and debugging (mocking, integration tests and how to use gdb on Rust, and so on)
  • Speeding up our Rust application

That's just a thought but looks good doesn't it?

That's it

That's it. Good luck everyone!