TL;DR: this is the question which you never ask in Rust. Instead Rust uses entirely different idea and the question becomes: what would prevent executor from moving the promise?
By that reasoning the whole Rust machinery is not needed at all. Smart people would write smart code and smart people wouldn't do dumb mistakes and everything would be just peachy.
That's the whole premise behind C and C++. And you know what? People are not smart enough, they are not careful enough, they do write stupid things by accidents if not on purpose.
You are looking in the wrong place and thinking about wrong things.
What is that future that async
function returns? Is it “specially crafted state machine that is esigned to do smart things”? Nope. Difference between fn foo
and async fn foo
is, in reality, pretty small: fn foo
places all it's variables on stack (using %sp
register on most architectures), async fn foo
places all it's variables in some other place (although optimizer may change it).
And you may write something like this:
async fn foo(x: Foo) {
let x: i32 = 42;
let y: &i32 = &x;
x.await;
println!("{x} {y}");
}
I mean: who would forbid that code and why?
Now the question arises: what if I would start such function, execute it for awhile, and then decide to postpone it. What happens in that case?
Well, now you have self-referential data structure which you may, suddenly, move somewhere. Because every type must be ready for it to be blindly memcopied to somewhere else in memory. But hey! That's not fair: if someone would move that future when it's executing… everything would fall apart.
It's not question of being new to Rust or something like that. You just have to understand what Rust is trying to achieve.
And it's goal is “simple”: reduce number of places where we would have to appeal to the smartness of people who are not supposed to be dumb things.
Even smartest people that I know sometimes do stupid mistakes. And compiler is supposed to stop them. But it may only do something if it would be told about capabilities of things that we are dealing with.
If we want to have posponeable function then we have to pick one of three options:
- Ensure that people couldn't use references that refer local variables (note that since
async fn
's are merged into one large blob in may not be truly local variable, but also a variable which you received via reference from another async fn
function).
- Change Rust fundamentally to make sure types may be moved in some “smart” fashion (think C++ move constructors).
- Invent some mechanism which would guarantee that self-referential data structure wouldn't be moves after creation even if complier would still be convinced that type is still always freely moveable.
Rust picked option #3 and [ab]used an interesting property of the future that's returned from async fn
: before you start execution of said function and pass arguments into it said future may be moved freely (because it's just an empty piece of memory without anything initialized in it) thus we may permit to return “raw” !Unpin
Future which would have to be processed unsafely at some point because of the bold sentence above.