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 unsafe doesn’t hurt, especially if it’s in common libraries like futures where it can be trusted to be correct. async also makes many custom Future impls 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 and async fn) that have custom Future and Stream impls.
-
It’s not strictly necessary to use unsafe. You could wrap all the inner Futures / Streams in a Pin<Box> layer to make the enum itself Unpin. Then you could write match &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 Unpin for 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 Pin API inside unsafe blocks 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::poll and Stream::poll impls (and Generator::resume in 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_mut to access their fields. While that is still way fewer uses of unsafe in comparison to enums (only one map_unchecked_mut per field, whereas enums require one get_unchecked_mut plus one new_unchecked per binding per variant), it still feels dissatisfactory to not have a complete solution.