About pin, what the last sentence means in the quoted document?

The pin document says:

Pin<P> can be used to wrap any pointer type P, and as such it interacts with Deref and DerefMut. A Pin<P> where P: Deref should be considered as a “P-style pointer” to a pinned P::Target – so, a Pin<Box<T>> is an owned pointer to a pinned T, and a Pin<Rc<T>> is a reference-counted pointer to a pinned T. For correctness, Pin<P> relies on the implementations of Deref and DerefMut not to move out of their self parameter, and only ever to return a pointer to pinned data when they are called on a pinned pointer.

I can understand the above document except the last sentence (which I make it bold).

Is this saying and only ever to return a mutable pointer to pinned data when they are called on a pinned pointer?

Because a mutable pointer can't be returned without consume the Pin<&mut T>, so, if we don't return such a mutable pointer when Pin<&mut T> is alive, we can assure T will not move around while Pin<&mut T> is alive.

But how could a shared pointer move or modify the pinned T? Why a shared pointer can't be returned?

What if I return a pointer to inner data when a Pin is alive? like the code below, is there something dangerous?

use std::ops::Deref;
use std::pin::*;

struct A;
impl A {
    fn a<'a>(self: &'a Pin<&'a Self>) -> &'a i32 {
        self
    }
    
    fn b(&self) -> &i32 {
        self
    }
}

impl Deref for A {
    type Target = i32;
    fn deref(&self) -> &Self::Target {
        &8
    }
}

fn main() {
    let mut a = A {};
    let x: Pin<&A> = Pin::new(&a);
    x.a();  // correct
    x.b();  // something wrong?
}

There is also some explanation about the interior mutability, which believes it is not a problem.

This is safe because it is not possible to move out of a shared reference. It may seem like there is an issue here with interior mutability: in fact, it is possible to move a T out of a &RefCell<T>. However, this is not a problem as long as there does not also exist a Pin<&T> pointing to the same data, and RefCell<T> does not let you create a pinned reference to its contents. See the discussion on “pinning projections” for further details.

The nightly docs explain this better: https://doc.rust-lang.org/nightly/std/pin/index.html

Pin<Ptr> requires that implementations of Deref and DerefMut on Ptr return a pointer to the pinned data directly and do not move out of the self parameter during their implementation of DerefMut::deref_mut. It is unsound for unsafe code to wrap pointer types with such “malicious” implementations of Deref; see Pin<Ptr>::new_unchecked for details.

So, first of all, you are correct in noticing that Deref::deref cannot move out of the underlying pointer. The docs now specifically point out that it's only deref_mut that could do the prohibited move.

Secondly, when they talk about moving out of the self parameter, they aren't talking about moving the pointer itself. They're talking about moving the pointee. The function DerefMut::deref_mut gets called with a reference to an object that has been pinned, even though Pin does not appear in its type signature.

pub struct EvilPtr<'a, T>(pub &'a mut T);
impl<'a, T> DerefMut for EvilPtr<'a, T> where T: Default {
    fn deref_mut(&mut self) -> &'a mut T {
        thread_local! {
            static EVIL_SLOT: RefCell<f32> = RefCell::new(T::default());
        }
        EVIL_SLOT.with_borrow_mut(|slot| std::mem::swap(&mut *evil_slot, self.inner));
        self.inner
    }
}
fn main() {
    let pinned_thing = std::marker::PhantomPinned; // this is just an example
    let mut pin_ref = unsafe { Pin::new_unchecked(EvilPtr(&mut pinned_thing)) };
    pin_ref.as_mut(); // Pin::as_mut transitively calls DerefMut::deref_mut
}

When we reach the third line, we've violated the pin invariant by moving the object around after it has been pinned. To avoid this, don't call Pin::new_unchecked with a smart pointer that does this.

2 Likes

It’s probably worth noting that this isn’t just a technicality— there are legitimate, though niche, uses for this technique. The one that comes to mind first is a smart pointer that provides access to some kind of on-disk cache: If the data has been evicted from memory since the last call to deref or deref_mut, they will need to be load it into a new allocation so that they can return a reference; this won’t generally be in the same place as before.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.