Some confusing about Pin & Box

I recently learned about Pin trait, and after rust nomicon doc & std::pin module doc, and also this topic

https://users.rust-lang.org/t/how-why-box-t-is-unpin-regardless-of-whether-t-is-unpin-or-unpin/59580

but still something confusing me.

my question is: if a struct is a self-ref, then we wrapper it into a box. So does there is a UB? below is my test code:



use std::{
    marker::{PhantomData, PhantomPinned},
    mem::{self, swap},
    ops::Deref,
    pin::Pin
};

struct SelfRef {
    age: i32,
    name: String,
    ptr: *const String,
    _marker: PhantomPinned,
}

impl SelfRef {
    fn new(name: String) -> SelfRef {
        let mut a = SelfRef {
            age: 8,
            name: name,
            ptr: std::ptr::null(),
            _marker: PhantomPinned,
        };
        let ptr: *const String = &a.name;
        a.ptr = ptr;
        a
    }

    fn ptr(&self) -> &String {
        unsafe { &*(self.ptr) }
    }

    fn name(&self) -> &String {
        &self.name
    }
}

fn main() {
    let ref1 = SelfRef::new("name".to_string());
    // println!("refs: {:p}", &ref1.age);
    // println!("refs: {:p}", ref1.ptr());
    // println!("refs: {:p}", ref1.name());
    let mut box_ref = Box::new(ref1);
    // println!("ref2's {:p}", &ref2.age);
    // println!("ref2's {:p}", ref2.name());

    box_ref.as_mut().name = "box_ref".to_string();
    println!("box_ref's {:?}", box_ref.ptr());
    println!("box_ref's {:?}", box_ref.name());

}

What you're doing is unsound: you can create the self-reference only after the data has been pinned, or at least put in a Box. In your case however you're creating ref1 on the stack, creating the self-reference (which points somewhere on the stack), and then move the data to the heap, while still keeping the self-referential pointer pointing to the stack. Thus, the pointer is invalid, and can't be used anymore. In fact then you print box_ref.ptr() you should see something like "\u{0}\u{0}\u{0}\u{0}", while it should actually print "box_ref" like box_ref.name().

Pin is a struct, not a trait. Unpin is a trait though.

1 Like

Thanks for your answer. Thanks a lot.

Yes, you said is right. and after a recall of async-book i got to know where i am wrong.

also your opinion about Pin and Unpin is right, thanks.

but as you say,
Does every type which one is self-ref struct, need self impl a Pin<Box> ?

also some questions about rust struct memory layout.
i am trying to explore rust memory layout, but it is hard after add some String field to struct. do you have some suggestion or advise (i did some google research, but had not a good answer)

I would say there are two situations:

  • the self-referential part references something that has a stable address, i.e. is already kinda pinned, for example the contents of a Box, of a String or of a Vec (note that you weren't referencing the contents!). In this case you don't need Pin because the data references is already pinned internally. This is the kind of self-referential type that you can make using the ouroboros crate for example;
  • the self-referential part references something that doesn't have a stable address (like in your example). In this case your type should initially not be self-referential and become one only by using a Pin<&mut Self>, whose existence guarantees that self is pinned. This doesn't need to be a Pin<Box<Self>> though, it can be any kind of Pin<...>. For example self-referential Futures use the Pin<&mut Self> given in the poll function.

You might try reading the Rustonomicon and parts of the Reference

2 Likes

Note that the contents of String and Vec only have a stable address between mutations: Even adding new items to the end might move everything.

2 Likes

Thanks