Does the documentation contains unnecessary `unsafe`?

I'm trying to understand the Pin conception. I read the doc and saw this simplified example:

struct Unmovable {
    data: String,
    slice: NonNull<String>,
    _pin: PhantomPinned,
}

impl Unmovable {
    fn new(data: String) -> Pin<Box<Self>> {
        let res = Unmovable {
            data,
            slice: NonNull::dangling(),
            _pin: PhantomPinned,
        };
        let mut boxed = Box::pin(res);

        let slice = NonNull::from(&boxed.data);
        unsafe {
            let mut_ref: Pin<&mut Self> = Pin::as_mut(&mut boxed);
            Pin::get_unchecked_mut(mut_ref).slice = slice;
        }
        boxed
    }
}

let unmoved = Unmovable::new("hello".to_string());
let mut still_unmoved = unmoved;
assert_eq!(still_unmoved.slice, NonNull::from(&still_unmoved.data));

It uses unsafe, although I tried to write a similar example and was able to do it without unsafe: Rust Playground.

It looks like this example is a bit overcomplicated. Is it worth using usafe if it's not necessary? I think this is not a very good code example, which only complicates understanding.

I agree that there's ways to improve the example. The power of Pin is to allow pinning without unconditionally requiring a heap allocation. API surfaces like Future can the leave it to the caller whether pinning happens on the heap or perhaps on the stack or nested in another structurally pinning data structure.

The example uses a heap allocation and does not involve any public API surfaces. Without any API surfaces, Pin does not do much besides perhaps communicating intent, and your assertion is correct that leaving the Pin creation to the end of the function and (technically) avoiding unsafe works just as well (even though of course the code is still safety relevant in relation with any code that dereferences the pointer). Instead of an Unmovable type whose only constructor creates a Pin<Box<Self>>, it would be also be just as safe to create an opaque BoxedUnmovable type that internally contains an ordinary Box, and where the inevitably unsafe (as raw pointers are involved) implementation will ensure that the contents are never moved, regardless; avoiding Pin entirely.

Downsides of a more natural example would be increased complexity: what I'm thinking of is something that splits a -> Self constructor (that leaves e. g. a null pointer or otherwise indicates that the eventually self-referencing pointer is not yet initialized) and a separate self: Pin<&mut Self> operation can do the initialization of the self-reference at a later time, just like Futures would do it in their poll method.

3 Likes

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.