Controversial opinion: keeping `Rc` across `await` should not make future `!Send` by itself

The problem with this is, as you correctly note about the Foo example, it's not about the types contained in the future, but what you do with them. To make Foo unsound it is sufficient to add fn get_a(&self) -> Rc<u32> (which would be a totally innocuous and normal thing to do if not for the unsafe impl Send). To make a Sendable Future containing Rc unsound it is sufficient for the future when polled to return or store an Rc "somewhere". (TLS isn't the only problem, either - what about the future's own Output?) That's not a property of Rc, it's a property of the future and its exact behavior when polled.

Answering the question "does this Rc get stored somewhere outside of this future" is a kind of escape analysis, and like most questions about the behavior of programs, it can only be answered for some programs, not all. (In jargon, it "reduces to the halting problem".) The Rust compiler doesn't do that kind of analysis... mostly. The main mechanism available to the compiler for proving that a function doesn't inappropriately return or store a thing in another thing is lifetimes. But Rc opts out of compiler tracking of lifetimes - from a certain perspective, that's the whole point of Rc. For the compiler to understand "don't let this reference escape this thing" you need to attach a lifetime to it - at which point, you might wonder, can I just do this with references anyway? And maybe that would be a still better answer.

Could the compiler do deep analysis on futures to see if Rcs escape? Sure, in principle. Should it though? Well, I don't know how generalizable it is, and it seems like a lot of work for dubious advantage.

8 Likes