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 Future
s / Stream
s 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 likefutures
where it can be trusted to be correct.async
also makes many customFuture
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 andasync fn
) that have customFuture
andStream
impls. -
It’s not strictly necessary to use
unsafe
. You could wrap all the innerFuture
s /Stream
s 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
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 insideunsafe
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
andStream::poll
impls (andGenerator::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 ofunsafe
in comparison to enums (only onemap_unchecked_mut
per field, whereas enums require oneget_unchecked_mut
plus onenew_unchecked
per binding per variant), it still feels dissatisfactory to not have a complete solution.