Honestly I’m still trying to wrap my head around what the proposal in this thread even is. We seem to be throwing every feature in the book at this problem with limited results. I’m going to discuss it using async fn, rather than generators, because they’re simpler and the differences don’t matter.
At first I thought the extern types and DynSized stuff didn’t do us anything at all. Here’s an alternative, with none of that, that seems provide the same guarantees:
async fn foo() -> i32 { .. }
// async fn returns:
struct AnonAnchor(AnonFuture);
impl Anchor<AnonFuture> for AnonAnchor {
// Never call this unless AnonAnchor has been pinned.
unsafe fn get_mut(&mut self) -> &mut AnonFuture { &mut self.0 }
}
// another compiler generator structure
enum AnonFuture { ... }
impl Future for AnonFuture { ... }
That is, AnonFuture is just a normal Sized type, but to get it you need to call get_mut on AnonAnchor, which is unsafe. It was hard for me to see what advantage the proposal here had over this, but eventually I figured it out - because the AnonFuture type does not implement some ?trait (I’m going to say ?Move, because I think the conflation between ?Move and ?DynSized is wrong & a distraction for other reasons), you can’t pass it to any API that might mem::swap it. This means that once you have &mut AnonFuture, you can stop worrying about safety because ?Move will take over.
Unfortunately, I don’t see how this meets our requirements:
- How do you call a combinator on the return type of an async function? You can’t as far as I can tell - because
AnonAnchor can’t implement Future, only AnonFuture can. There’d have to be some sort of map function (which would have to be unsafe for the same reason Pin::map is unsafe) to go from AnonAnchor<AnonFuture> to AnonAnchor<Combinator>. I don’t see how you call combinators on an async fn without unsafe code under this proposal.
- Even setting aside that, now every combinator that wants to be able to handle an async fn has to be add the
+ ?Move bound. This is essentially just the opposite of today, where if they want to do something that doesn’t support async fn, they have to add + Unpin bounds. But most combinators support async fn fine, so this is a net loss.
What you’ve done, as far as I can tell, is change the definition of ?Move slightly, and make it safe by adding this Anchor indirection. In the original proposal, ?Move did not mean “can never be moved,” it meant “can not be moved after the addressof operator has been applied to it.” By conflating ?Move and ?DynSized, you suggest a definition in which it can never be moved, in which case you need this anchor type to be able to move it around before you box it. But having done that, you have not actually solved the combinator problem, which the original ?Move proposal did at least solve.
(You have, however, solved the backcompat problem with ?Move -Fn types don’t need to return a type that doesn’t implement Move - so that’s something.)
And I’m still concerned about adding ?traits to the language at all, because of how infectious they are. Any API that takes a generic by reference now needs to consider not only if needs the reference to be Sized, but if it needs it to be DynSized or Move or whatever additional ?traits we add. In contrast, the impact of Pin is well-contained to the APIs that care about it).
All in all, I am not optimistic that this will provide a more fruitful approach than Pin.