Guillaume Lemaître
Guillaume Lemaître

Reputation: 1280

Is it possible to create a self-referential struct on the stack instead of the heap using Pin?

Is it possible to allocate self-referential structs on stack? The pin documentation shows an example of pinning a struct on the heap. I followed it to write corresponding code:

pub struct Cont {
    pub f: i32,
    // shall point to 'f' field
    ptr_f: NonNull<i32>,
}

impl Cont {
    pub fn read(&self) -> i32 {
        unsafe { *self.ptr_f.as_ref() }
    }

    pub fn pinned(value: i32) -> Pin<Self> {
        // ensures ptr_f = &f
    }
}

fn main() {
    let a = Cont::pinned(5);
    let b = Cont::pinned(12);

    assert_eq!(a.f, a.read());
    assert_eq!(b.f, b.read());
}

but I don't know how to write the Cont::pinned function, or even if it's the right signature (if even possible).

Upvotes: 3

Views: 807

Answers (1)

Peter Hall
Peter Hall

Reputation: 58805

but I don't know how to write the Cont::pinned function, or even if it's the right signature (if even possible).

The type parameter to Pin<P> is always a pointer or reference; a Pin never owns its data, except via an owning pointer type such as Box. Given that you want to keep the original value on the stack, the analogue of the function in the example is:

fn pinned(value: i32) -> Pin<&mut Self>;

But this isn't possible because a function can't return a reference to something it created - unless that something is stored on the heap or in static memory. So, if you were to construct a Pin of a stack-allocated value, you'd have to create the unpinned value first, so that that the pin can reference it.

Perhaps you might try to design an API that creates a Pin by mutating some data that is already on the stack:

let a: Option<Cont> = None;
let b: Option<Cont> = None;

let a = Cont::pinned(&mut a, 5);
let b = Cont::pinned(&mut b, 12);

But then the value would live longer than the pin and you can only enforce the pin guarantees while the pin is live, making this unsound.

To make it sound, you would need to somehow enforce that the original values cannot be accessed after the pin is dropped. This would result in a very constrained API. For example:

fn main() {
    // setup_conts creates the pinned Cont values and calls the closure 
    setup_conts(5, 12, |a: Pin<&mut Cont>, b: Pin<&mut Cont>| {
        // a and b can only be used inside this closure
    })
}

Upvotes: 3

Related Questions