When I define an async function in main.rs, the async function generates a poll function and a context initialization function, like this:
pub async fn hello_world()
{
println!("hello world!!!");
}
#[tokio::main]
async fn main() {
hello_world().await;
}
From the symbol table, it can be seen that async hello_world function generates two functions: Poll function in Future trait and context initialization function. Use command readelf -sW hello_world to see the names in the symbol table are _ZN15test_helloworld11hello_world28_$u7b$$u7b$closure$u7d$$u7d$17hfb37a71ab872a722E
and
_ZN15test_helloworld11hello_world17h68b666166140c723E.
But when I defined asyn functions in lib.rs, there was no poll function,like this:
pub async fn hello_world()
{
println!("hello world!!!");
}
pub async fn start_world()
{
hello_world().await;
}
After compiling rust lib into an obj using the command cargo rustc -- --emit=obj, it can be seen from the symbol table that the hello_worldfunction only has a context initialization function and no Future poll function. Use command readelf -sW xxx.o to see the names in the symbol table, only has _ZN14helloworld_lib11hello_world17h3e453d3cd706914eE, Missing poll function in obj.
This makes me very confused. In rust, async is a syntax sugar that will generate a poll function after disaggregating the sugar, but why did the compiler not generate it? When was this poll function generated? Can I add compilation options to allow the rust compiler to generate this poll function?
Async functions return types that implement the
Futuretrait. Machine code for trait implementations may not be generated until it is needed, by some use of the trait implementation; in this case, the compiler compiling the use-site gets the implementation code as Rust MIR (mid-level intermediate representation), not machine code. (I don't know why the compiler makes this choice; it may be because generic code is very often a candidate for inlining, so emitting machine code functions that would be rarely called is not worthwhile.)We can force an implementation to be included by writing a plain function that explicitly returns a
dyn Future. Vtables fordyndispatch are generated when, and only when, the Rust code coerces a concrete type (such as the return type ofstart_world()) to adyntype (such asdyn Future), so writing the following function will force a vtable, and the functions in it, to be generated, because returning theBoxperforms such a coercion:(Pinning isn't necessary for this demonstration, but would be present in any practical function returning this type. It doesn't change the machine code.)
Now let's look at the generated code with
cargo-show-asm. You don't need to know very much about the assembly language for your processor (in this example, x86) to be able to tell what is present, because you can just look for the presence of interesting labels/symbols.concrete_start()appears:Of course, most of this is the
Boxallocation. Butl___unnamed_1is in the vtable for theFutureimplementation:This vtable consists of a pointer to the type's drop glue (a function that knows to drop/destruct/deallocate it), its size and alignment, and a pointer to the
Future::poll()implementation:And there's your
println!()call, as well as a bunch of checks for invalid state transitions, like theFuturebeing polled after it completes. Here's the message strings for those panics: