This really cries out for a way to mark methods as accessing disjoint fields. After all, there is no inherent need for the Cell, since poll is called with a unique reference. We could avoid it by adding methods to access the other two fields:
fn get_t1<'a>(self: Pin<&'a mut Self>) -> &'a mut Option<T1> {
unsafe { &mut self.get_unchecked_mut().2 }
}
…and this would be nice and regular and possible to automate using a macro. But with these types of methods, you can’t mutably borrow more than one field at the same time.
I suppose an alternative is to have one method that borrows all the fields:
struct JoinFuture<T1,T2,F1,F2> {
f1: F1,
f2: F2,
t1: Option<T1>,
t2: Option<T2>,
}
struct JoinFutureBorrowMut<'a, T1,T2,F1,F2> {
f1: Pin<&'a mut F1>,
f2: Pin<&'a mut F2>,
t1: &'a mut Option<T1>,
t2: &'a mut Option<T2>,
}
impl<T1,T2,F1,F2> JoinFuture<T1,T2,F1,F2> {
fn borrow_mut<'a>(self: Pin<&'a mut Self>) -> JoinFutureBorrowMut<'a, T1, T2, F1, F2> {
unsafe {
let this = self.get_unchecked_mut();
JoinFutureBorrowMut {
f1: Pin::new_unchecked(&mut this.f1),
f2: Pin::new_unchecked(&mut this.f2),
t1: &mut this.t1,
t2: &mut this.t2,
}
}
}
}
(Again, this is amenable to implementation via macro.)
With this, I was able to both avoid the Cell and make the poll implementation less verbose overall:
fn poll(self: Pin<&mut Self>, lw: &LocalWaker) -> Poll<Self::Output> {
let this = self.borrow_mut();
match (&this.t1, &this.t2) {
(Some(_), Some(_)) => unreachable!(),
(Some(_), _) => match this.f2.poll(lw) {
Ready(v2) => Ready((this.t1.take().unwrap(), v2)),
_ => Pending
},
(_, Some(_)) => match this.f1.poll(lw) {
Ready(v1) => Ready((v1, this.t2.take().unwrap())),
_ => Pending
},
_ => match (this.f1.poll(lw), this.f2.poll(lw)) {
(Ready(v1),Ready(v2)) => Ready((v1, v2)),
(Ready(v1), _) => { *this.t1 = Some(v1); Pending },
(_, Ready(v2)) => { *this.t2 = Some(v2); Pending },
_ => Pending,
}
}
}
Playground link