That's the basic tenet of Rust's safety: unsafe code can never trust safe code, safe code can do whatever the safe API allows. That's what "soundness" is about, and it's quite shocking to hear you, of all people, propose that we should encourage unsound code, and that the safe code must bend over backwards to uphold unwritten expectations. You may change the std, but you can't change all other crates, which reasonably expect that the unsafe code is sound. It looks like a "not our problem" approach.
There are legitimate use cases, but they admit easy sound workarounds.
If the captures are borrowed, then I can't imagine a situation where pinning them outside the closure and capturing Pin<&mut T>
instead of &mut T
could be restrictive. This is also the case which is required for the join!
macro.
If the capture is moved, then demanding an external pin is somewhat more restrictive, since you can't just pin it to the stack during execution. You may also require more space in the closure, if you're trying to pin a value of size larger than size_of::<usize>()
. I don't consider it a significant enough restriction to warrant unsound code. The size specifically is not an issue, since async wastes way more space.
The workaround is also simple. You either pin to the heap, or use the trick similar to the suggestion by @pcpthm :
let mut pinned = Box::pin(async move {
// this is just `pin_mut!(trouble)`
let trouble = unsafe { Pin::new_unchecked(&mut trouble) };
std::future::poll_fn(move |cx| trouble.poll(cx)).await
});
You are no worse than with PollFn<F>: !Unpin
. I also don't think that the case of pinning to the heap (or an arena) being unacceptable is a common one. Most users of async can just Box::pin(trouble)
and be done with it.
A use case which isn't covered by the above is when you replace the capture within the closure depending on some condition, and then pin it. Technically, it's possible to do both mutation and pinning of the same value within the closure in a safe way, depending on the values of other captures and some analysis of runtime behaviour. I do not consider it a legitimate use case, because it is brittle. It also can't be safely expressed with FnPinMut
closures, because you would have to move a pinned value. It's very unsafe no matter how you deal with closures, and it's better to mutate the value via Pin::get_unchecked_mut
, which explicitly signals fishy behaviour, rather than the current status quo where you could hide the brittle unsafety behind an unassuming pin_mut!
.