-
Notifications
You must be signed in to change notification settings - Fork 16
Description
I have used pin-utils extensively on a Pin-heavy library.
Overall I found it rather unergonomic, so I created a new macro:
#[macro_export]
macro_rules! unsafe_project {
(@parse $value:expr,) => {};
(@parse $value:expr, pin $name:ident, $($rest:tt)*) => {
#[allow(unused_mut)]
let mut $name = unsafe { ::std::pin::Pin::new_unchecked(&mut $value.$name) };
$crate::unsafe_project! { @parse $value, $($rest)* }
};
(@parse $value:expr, mut $name:ident, $($rest:tt)*) => {
#[allow(unused_mut)]
let mut $name = &mut $value.$name;
$crate::unsafe_project! { @parse $value, $($rest)* }
};
($value:expr => { $($bindings:tt)+ }) => {
let value = unsafe { ::std::pin::Pin::get_unchecked_mut($value) };
$crate::unsafe_project! { @parse value, $($bindings)+ }
};
}
Here is an example of using it:
unsafe_project!(self => {
pin signal1,
pin signal2,
mut left,
mut right,
mut callback,
});
Basically, you pass in something which is pinned (in this case self
), and then it will destructure the individual fields (which are marked as either pin
or mut
).
Using pin
is similar to unsafe_pinned
and using mut
is similar to unsafe_unpinned
.
Here is an example of real code using the pin-utils macros (unsafe_pinned
and unsafe_unpinned
):
#[derive(Debug)]
#[must_use = "Futures do nothing unless polled"]
pub struct CancelableFuture<A, B> {
state: Arc<CancelableFutureState>,
future: Option<A>,
when_cancelled: Option<B>,
}
impl<A, B> CancelableFuture<A, B> {
unsafe_unpinned!(state: Arc<CancelableFutureState>);
unsafe_pinned!(future: Option<A>);
unsafe_unpinned!(when_cancelled: Option<B>);
}
impl<A, B> Unpin for CancelableFuture<A, B> where A: Unpin {}
impl<A, B> Future for CancelableFuture<A, B>
where A: Future,
B: FnOnce() -> A::Output {
type Output = A::Output;
fn poll(mut self: Pin<&mut Self>, waker: &LocalWaker) -> Poll<Self::Output> {
if self.as_mut().state().is_cancelled.load(Ordering::SeqCst) {
Pin::set(self.as_mut().future(), None);
let callback = self.as_mut().when_cancelled().take().unwrap();
Poll::Ready(callback())
} else {
match self.as_mut().future().as_pin_mut().unwrap().poll(waker) {
Poll::Pending => {
*self.as_mut().state().waker.lock().unwrap() = Some(waker.clone().into_waker());
Poll::Pending
},
a => a,
}
}
}
}
And the same code with my unsafe_project
macro:
#[derive(Debug)]
#[must_use = "Futures do nothing unless polled"]
pub struct CancelableFuture<A, B> {
state: Arc<CancelableFutureState>,
future: Option<A>,
when_cancelled: Option<B>,
}
impl<A, B> Unpin for CancelableFuture<A, B> where A: Unpin {}
impl<A, B> Future for CancelableFuture<A, B>
where A: Future,
B: FnOnce() -> A::Output {
type Output = A::Output;
fn poll(self: Pin<&mut Self>, waker: &LocalWaker) -> Poll<Self::Output> {
unsafe_project!(self => {
mut state,
pin future,
mut when_cancelled,
});
if state.is_cancelled.load(Ordering::SeqCst) {
Pin::set(future, None);
let callback = when_cancelled.take().unwrap();
Poll::Ready(callback())
} else {
match future.as_pin_mut().unwrap().poll(waker) {
Poll::Pending => {
*state.waker.lock().unwrap() = Some(waker.clone().into_waker());
Poll::Pending
},
a => a,
}
}
}
}
There are three huge advantages to this:
-
It's overall much shorter. Rather than using
self.as_mut().future()
you just usefuture
. This makes working with pinned structs almost as convenient as unpinned structs! -
unsafe_pinned
andunsafe_unpinned
require you to specify the bounds/types of the fields twice. But withunsafe_project
you don't need to. -
It fully supports disjoint borrows, unlike
unsafe_pinned
andunsafe_unpinned
.This allowed me to take this horrible code... and replace it with this lovely code.
In addition, there are a couple places where I need disjoint borrows, like here. Using
unsafe_project
works great, but I cannot useunsafe_pinned
andunsafe_unpinned
for this.
I ported my entire codebase to use unsafe_project
, overall I'm very happy with it.
The only downside I found is that you can't call other self
methods at the same time as using unsafe_project
:
unsafe_project!(self.as_mut() => {
pin signal1,
pin signal2,
mut left,
mut right,
mut callback,
});
// Error: doesn't work, since `self` is borrowed
self.foo();
Are you interested in adding in a macro like unsafe_project
into pin-utils?