Borrow of owned !Sync type in async function

Welcome to the world of async. Whatever intuitions you might have developed up to this point are better off neatly packaged, tied with a ribbon on top, then promptly thrown out of a window.

Any async functionasync fn(A) → B is syntactic sugar for fn(A) → impl Future<Output = B>. The “ownership” you are trying to carry over from the sync side doesn’t apply here.

Any F: impl Future itself isn’t a singular memory location tagged with a few impl's here and there. Rather: it’s a compiler-produced state machine that provides no guarantees whatsoever as to where or when or how, thread/execution/timing-wise, any next state snapshot is to be handled.

Meaning: the compiler has to assume the worst case scenario. As in: each and every await point has to be considered a potential thread hand-off of the state A created by that await point into the state B which follows next.

Since both a: Cell<i32> and b: &Cell<i32>in your scenario are (to be thought of as) initialized in a thread X and processed after the await in the println!(...) by a thread Y: you’ve got yourself a perfectly reasonable violation of the Sync and Send trait bounds.

In your particular example, it’s easy enough to solve. Simply hand out the ownership over a itself instead of the its reference &b. This compiles fine too. For the future reference (pun intended), when dealing with impl Future in any shape or form, think less about the ownership over a singular memory location (sync) and more about the implicit synchronicity over the data captured within/by any particular await point of the Future itself. More often than not, it’ll help.

Should you happen to feel momentarily distraught by all the aforementioned, tough luck. Better yet: let me channel my inner Bethesda’s customer service from the times of Fallout 76:

Hello,

We are sorry you aren’t happy with the state of the async in the current edition of Rust. The memory ownership intuition you were meant to develop when working with single-threaded and/or parallel execution turned to be too expensive to port into our zero-cost concurrency framework, reinvented from scratch for the ultimate benefit to no one in particular.

We aren’t planning to do anything about it.

Rust Async Support - International Department

5 Likes