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 Send
able 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 Rc
s 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.