Skip to content

Commit 3278dea

Browse files
authored
Rollup merge of rust-lang#103396 - RalfJung:pinning-closure-captures, r=dtolnay
Pin::new_unchecked: discuss pinning closure captures Regardless of how the discussion in rust-lang#102737 turns out, pinning closure captures is super subtle business and probably worth discussing separately.
2 parents b7bc90f + 964290a commit 3278dea

File tree

1 file changed

+55
-2
lines changed

1 file changed

+55
-2
lines changed

library/core/src/pin.rs

+55-2
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,7 @@ impl<P: Deref> Pin<P> {
543543
/// let p: Pin<&mut T> = Pin::new_unchecked(&mut a);
544544
/// // This should mean the pointee `a` can never move again.
545545
/// }
546-
/// mem::swap(&mut a, &mut b);
546+
/// mem::swap(&mut a, &mut b); // Potential UB down the road ⚠️
547547
/// // The address of `a` changed to `b`'s stack slot, so `a` got moved even
548548
/// // though we have previously pinned it! We have violated the pinning API contract.
549549
/// }
@@ -563,13 +563,66 @@ impl<P: Deref> Pin<P> {
563563
/// // This should mean the pointee can never move again.
564564
/// }
565565
/// drop(pinned);
566-
/// let content = Rc::get_mut(&mut x).unwrap();
566+
/// let content = Rc::get_mut(&mut x).unwrap(); // Potential UB down the road ⚠️
567567
/// // Now, if `x` was the only reference, we have a mutable reference to
568568
/// // data that we pinned above, which we could use to move it as we have
569569
/// // seen in the previous example. We have violated the pinning API contract.
570570
/// }
571571
/// ```
572572
///
573+
/// ## Pinning of closure captures
574+
///
575+
/// Particular care is required when using `Pin::new_unchecked` in a closure:
576+
/// `Pin::new_unchecked(&mut var)` where `var` is a by-value (moved) closure capture
577+
/// implicitly makes the promise that the closure itself is pinned, and that *all* uses
578+
/// of this closure capture respect that pinning.
579+
/// ```
580+
/// use std::pin::Pin;
581+
/// use std::task::Context;
582+
/// use std::future::Future;
583+
///
584+
/// fn move_pinned_closure(mut x: impl Future, cx: &mut Context<'_>) {
585+
/// // Create a closure that moves `x`, and then internally uses it in a pinned way.
586+
/// let mut closure = move || unsafe {
587+
/// let _ignore = Pin::new_unchecked(&mut x).poll(cx);
588+
/// };
589+
/// // Call the closure, so the future can assume it has been pinned.
590+
/// closure();
591+
/// // Move the closure somewhere else. This also moves `x`!
592+
/// let mut moved = closure;
593+
/// // Calling it again means we polled the future from two different locations,
594+
/// // violating the pinning API contract.
595+
/// moved(); // Potential UB ⚠️
596+
/// }
597+
/// ```
598+
/// When passing a closure to another API, it might be moving the closure any time, so
599+
/// `Pin::new_unchecked` on closure captures may only be used if the API explicitly documents
600+
/// that the closure is pinned.
601+
///
602+
/// The better alternative is to avoid all that trouble and do the pinning in the outer function
603+
/// instead (here using the unstable `pin` macro):
604+
/// ```
605+
/// #![feature(pin_macro)]
606+
/// use std::pin::pin;
607+
/// use std::task::Context;
608+
/// use std::future::Future;
609+
///
610+
/// fn move_pinned_closure(mut x: impl Future, cx: &mut Context<'_>) {
611+
/// let mut x = pin!(x);
612+
/// // Create a closure that captures `x: Pin<&mut _>`, which is safe to move.
613+
/// let mut closure = move || {
614+
/// let _ignore = x.as_mut().poll(cx);
615+
/// };
616+
/// // Call the closure, so the future can assume it has been pinned.
617+
/// closure();
618+
/// // Move the closure somewhere else.
619+
/// let mut moved = closure;
620+
/// // Calling it again here is fine (except that we might be polling a future that already
621+
/// // returned `Poll::Ready`, but that is a separate problem).
622+
/// moved();
623+
/// }
624+
/// ```
625+
///
573626
/// [`mem::swap`]: crate::mem::swap
574627
#[lang = "new_unchecked"]
575628
#[inline(always)]

0 commit comments

Comments
 (0)