Apologies if this has been discussed already. I didn’t find anything in GH issues or the Pin RFC or in this forum.
Consider the case of implementing Future / Stream on an enum that wraps Futures / Streams in its variants, and wants to impl poll() by forwarding to the variants. Currently you have to use unsafe Pin::get_unchecked_mut + Pin::new_unchecked to match on the enum and access the variant fields.
futures 0.3’s impl of Stream on either::Either is a good example:
fn poll_next(self: Pin<&mut Self>, lw: &LocalWaker) -> Poll<Option<A::Item>> {
unsafe {
match Pin::get_unchecked_mut(self) {
Either::Left(a) => Pin::new_unchecked(a).poll_next(lw),
Either::Right(b) => Pin::new_unchecked(b).poll_next(lw),
}
}
}
It would be nice if match ergonomics was enhanced to let you write:
match self {
Either::Left(a /* : Pin<&mut A>*/ ) => a.poll_next(lw),
Either::Right(b /* : Pin<&mut B>*/ ) => b.poll_next(lw),
}
so that you didn’t have to use the unsafe Pin API.
In general, code like this:
match some_pinned_value {
Enum::A(a, b, c) => { body },
Enum::B(d, e, f) => { body },
}
would expand to something like:
match unsafe { Pin::get_unchecked_mut(some_pinned_value) } {
Enum::A(a, b, c) => { let (a, b, c) = unsafe { (Pin::new_unchecked(a), Pin::new_unchecked(b), Pin::new_unchecked(c)) }; body },
Enum::B(d, e, f) => { let (d, e, f) = unsafe { (Pin::new_unchecked(d), Pin::new_unchecked(e), Pin::new_unchecked(f)) }; body },
}
This also generalizes to more complicated / multiple-level nested patterns like Enum::C(Some(Struct { field1 }))
Counter-points to this idea:
-
A little
unsafedoesn’t hurt, especially if it’s in common libraries likefutureswhere it can be trusted to be correct.asyncalso makes many customFutureimpls unnecessary.But I believe custom impls in user code will still be common enough that it would be a nice to not require
unsafe. I personally have crates on both stable (using futures 0.1) and nightly (using futures 0.3 andasync fn) that have customFutureandStreamimpls. -
It’s not strictly necessary to use
unsafe. You could wrap all the innerFutures /Streams in aPin<Box>layer to make the enum itselfUnpin. Then you could writematch &mut *self { Either::Left(a) => a.as_mut().poll_next(lw), ... }.But the enum value itself is going to be pinned anyway, so deliberately making it
Unpinfor the sake of simpler syntax seems wasteful. -
This is more complicated and magical than the transformation that match ergonomics does right now. It has to insert calls to
PinAPI insideunsafeblocks inside each match arm, and also around the matched expression itself. -
Related to the previous point, this feature probably won’t see any use outside of
Future::pollandStream::pollimpls (andGenerator::resumein the future). Is the complication worth it? -
This doesn’t help with the other type of custom impls - impls on structs - since those must still use the unsafe
Pin::map_unchecked_mutto access their fields. While that is still way fewer uses ofunsafein comparison to enums (only onemap_unchecked_mutper field, whereas enums require oneget_unchecked_mutplus onenew_uncheckedper binding per variant), it still feels dissatisfactory to not have a complete solution.