Help to design a pinned object

I'm dealing with some FFI codes, so I need to use pointers, now I have a struct like this:

struct Foo {
    i_pointer: *const i32, // just for example, the real case is *const c_void
    i: i32,
}

impl Foo {
    pub fn new() -> Arc<Foo> {
        let mut arc_foo = Arc::new(Foo {
            i_pointer: std::ptr::null(),
            i: 0,
        });
        let foo = Arc::get_mut(&mut arc_foo).unwrap();
        foo.i_pointer = &foo.i as *const _;
        arc_foo
    }
}

And for more safety, since this is a typical self-referenced struct, I decide to use Pin to forbid the movement of the object.

Since Pin has no effort on Unpin objects, the first step I decide to do is make my type cancel the Unpin trait implement like this:

struct Foo {
    i_pointer: *const i32,
    i: i32,
    _pin: PhantomPinned,
}

And the new function become to this:

impl Foo {
    pub fn new() -> Arc<Pin<Foo>> {
        let mut arc_foo = Arc::new(Pin::new(Foo {
            i_pointer: std::ptr::null(),
            i: 0,
            _pin: Default::default()
        }));
        let pin = Arc::get_mut(&mut arc_foo).unwrap();
        ...
        arc_foo
    }
}

But I suddenly have no idea how to write.
The first problem is that the type Foo is not implement Deref:

error[E0277]: the trait bound `Foo: Deref` is not satisfied
   --> src\main.rs:22:45
    |
22  |           let mut arc_foo = Arc::new(Pin::new(Foo {
    |  ____________________________________--------_^
    | |                                    |
    | |                                    required by a bound introduced by this call
23  | |             i_pointer: std::ptr::null(),
24  | |             i: 0,
25  | |             _pin: Default::default()
26  | |         }));
    | |_________^ expected an implementor of trait `Deref`
    |
note: required by a bound in `Pin::<P>::new`
   --> C:\Users\psion\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib/rustlib/src/rust\library\core\src\pin.rs:482:9
    |
482 | impl<P: Deref<Target: Unpin>> Pin<P> {
    |         ^^^^^^^^^^^^^^^^^^^^ required by this bound in `Pin::<P>::new`
help: consider borrowing here
    |
22  |         let mut arc_foo = Arc::new(Pin::new(&Foo {
    |                                             +
22  |         let mut arc_foo = Arc::new(Pin::new(&mut Foo {
    |                                             ++++

But I don't know how to write, since I already use _pin: PhantomPinned, to cancel the Unpin impl, how can I make a Unpin deref?

So what is the right way to design a fn new() -> Arc<Pin<Foo>> in my case?

Silly question, but is Pin actually adding value here, or will it just add extra friction and potential for footguns?

I would traditionally handle a problem like this by making sure the public API is sound and hiding implementation details. From there, you can run miri over your test suite to make sure things are correct.

Maybe it's overdesigned, but it is designed as a public API, and there are indeed safe ways to get a &mut T from an Arc, I'm afraid some user will really use this by accident, if the object moved, the program will definitely crash.

The Pin goes around the Arc.

1 Like

The "PhantomPinned cannot be unpinned" error still exists.

You can only use Pin::new when the type is Unpin. Consider using the unsafe constructer? Or maybe Arc has an Arc::pin built in?

1 Like

Indeed, an Arc::pin() method does exist for this purpose.

Note that in this case, it is necessary that Pin::new doesn't work, because otherwise you could have created clones of the Arc before you pinned it, and the other clones wouldn't know it had been pinned.

1 Like

I'm a little confused here, the doc said

This is a wrapper around a kind of pointer which makes that pointer β€œpin” its value in place, preventing the value referenced by that pointer from being moved unless it implements Unpin .

But if the Pin::new API only accept Unpin objects, what the point of Pin? How are !Unpin objects interact with Pin? Does all !Unpin interact with Pin by using unsafe function Pin::new_unchecked which I guess is a warning that pin a !Unpin object is dangers, you should be careful?

If creating a Pin is unsafe, then you can say that it's the fault of that unsafe block if the value is moved later. So it doesn't actually prevent moves β€” it just changes who gets the blame if a move happens.

Constructing an arbitrary Pin<P> with Pin::new_unchecked() is unsafe because it relies on a property of the pointer type P. That's why Box::pin() and Arc::pin() exist and are safe: so that you can have Box or Arc make the promise that they have that property.

That's another confusion of mine: Pin is used for dealing with the self-reference problem, but you cannot write code like this:

let mut arc_foo = Arc::new(Foo {
            i_pointer: &self.i, // won't work
            i: 0,
        });

So after creating an Arc or a Box, some stuff must be done to assign the right value to i_pointer:

let foo = Arc::get_mut(&mut arc_foo).unwrap();
foo.i_pointer = &foo.i as *const _;

But if I use a Box::pin or Arc::pin, the modify step will not be done. So why should I use these two functions at all?

If T: !Unpin, then Box::pin() and Arc::pin() are the only safe ways of creating a Pin<Box<T>> or Pin<Arc<T>> from a T. You could also do it manually with Pin::new_unchecked(), but you'd have to ensure with an unsafe block that nothing else has access to the pinned fields.

I don't think you answered my question, Yes Box::pin() and Arc::pin() are safe, but how you can do the self-reference modification by using these two functions?

Pin doesn't let you write self-refential stuff safely. Rather, it's that without Pin, you couldn't do it even when using unsafe.

True, creating the value and subsequently pinning it likely works better for Pin<Arc<T>>. But for Pin<Box<T>> and Pin<&mut T>, it can be done safely with crates such as pin-project.

1 Like

There are various safe abstractions built on top of Pin, yes. However, pin-project is only a partial example - it lets you put other self-referential structs in your struct, but doesn't help if you're writing the original source of self-reference.

1 Like

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.