How can Rust async work without Future?

On the code below, I did not import a/the Future trait:

async fn hello_world() {
    println!("hello, world!");
}

fn main() {
    let future = hello_world(); 
}

and yet it still compiles. However it won't print anything.

According to https://smallcultfollowing.com/babysteps/blog/2019/10/26/async-fn-in-traits-are-hard/:

impl MyDatabase {
    async fn get_user(&self) -> User {
        ...
    }
}

This would get desugared to something similar to:

impl MyDatabase {
    fn get_user(&self) -> impl Future<Output = User> + '_ {
        ... 
    }
}

How can this desugar be done if no Future trait is imported? More importantly, why the Future trait isn't defined in Rust itself, but instead is a crate?

More accurately it gets desugared to something like:

impl MyDatabase {
    fn get_user(&self) -> impl ::core::future::Future<Output = User> + '_ {
        ... 
    }
}

Because it uses an absolute path, it's able to access Future from anywhere instead of just when Future is imported.

Future is defined in libcore, which is both a crate and part of Rust itself. It has a #[lang = "future_trait"] attribute which tells Rustc "this is the type you need to use to desugar async fns with".

1 Like

Or in other words, core is pretty much a subset of the standard library. That’s why you’ll also find Future as std::future::Future in the standard library. The standard library should be considered part of the Rust programming language (although there’s also no_std Rust, but which is by-the-way the reason why core exists).

There are many other types and traits that are part of the language. Lots of traits in std::ops including traits for overloading syntax such as a + b or c[d] and the Fn traits; types such as Box that has magic powers like dereferencing by-value; for syntax which uses IntoIterator and pattern matches on an Option, etc.


Also regarding this question

there’s not really anything like “importing” in Rust. A use statement such as use std::future::Future does nothing more than introduce a shorthand, namely Future, for a longer path std::future::Future that was already in scope. The closest thing to “importing” is probably adding dependencies on crates. In this regard, the standard library is always present, as an implicit dependency.

Also, you might have referred to futures::future::Future. This is just a re-export (e.g. click on the [src] button so see it being defined in core) of the trait from the standard library. I’m not entirely sure of the details but I believe that, historically, an older version of the Future trait was present in this crate before async was first added to the language; but as soon as you’re having async syntax, the Future trait can’t be coming from an external crate anymore.

2 Likes

Future is a "lang item", so knowledge about its existence is hardcoded in the compiler.

https://doc.rust-lang.org/stable/src/core/future/future.rs.html#30

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.