Can you get the raw pointer of a pinned Arc?

When wrapping a C library that takes (and keeps) a context pointer:

void register_callback(callback_type cb, void* context);

I usually use an Arc and give the raw value to the lib, like:

let foo = Arc::new(Foo::new());

let foo_c = foo.clone();
let context = Arc::into_raw(foo_c) as *mut c_void;

unsafe { register_callback(on_callback, context); }

I saw the new Pin functionality and thought that would be better and safer. Is that true? Would it be better to pin the Arc?

If so, and I start with:

 let foo = Arc::pin(Foo::new());

How would I get a raw value out of it properly?

Thanks!

I’m not entirely sure if Arc ever relocates memory. The pin only makes sure, that the Arc struct itself never moves, not the inner data. So you could just use Arc::into_raw and you should be fine. Of course only, if your inner data won’t move, which means it must be allocated on the heap or a Pin itself.

Does that make sense to you? :confused:

This is incorrect, Pin<Arc<T>> means that it is guaranteed that T must not move in memory (Pin is a modifier to a pointer type, it holds the pointer type by value so the pointer itself will move when the Pin value is moved).

Arc<T> on its own does provide the guarantee that the memory backing it won’t move (otherwise it couldn’t provide a very useful Arc::into_raw), so you can just directly use it for these usecases. Using Pin<Arc<T>> wouldn’t be wrong here since it has stronger guarantees, but I don’t think it’s necessary and there isn’t yet much of a convention to use it.

In terms of getting the raw pointer there’s an unstable API for it:

/// I promise I will uphold the pinning guarantees on this raw pointer
unsafe {
     Arc::into_raw(Pin::into_inner_unchecked(foo.clone()))
}

or you can just directly convert a reference into the inner value into a raw pointer

/// I promise I will uphold the pinning guarantees on this raw pointer
unsafe {
    let foo = foo.clone();
    let ptr = &*foo as *const _ as *mut _;
    mem::forget(foo); // forget this `Arc` so that it will not decrement the refcount
    ptr
}

You don’t show any reason to use Pin.

In terms of making it safer, you eliminate the use of unsafe’s. You can’t get rid of the unsafe call to ffi register_callback but if the context is only read in Rust code then it can be a handle (cast from usize) rather than a direct pointer; no need then for from_raw.

Well the code has been working fine for quite a while now, and I wasn’t worried about it… until Pin appeared.

I haven’t looked at the inner workings of Arc, but I agree that even if it could move memory, there should be an explicit guarantee that it couldn’t happen so long as a raw pointer is floating around: meaning that a call to into_raw should pin the memory. Ditto for Box::into_raw for that matter.

The idea of a handle is an interesting alternative; I hadn’t thought of that. But it opens all the problems of a shared, global, mutable collection.

For now, I suppose I will keep it the way it is, until some change to the library breaks it.

Thanks!

In Rust there are two ways of accessing data:

  • either in the presence of potential aliasing (&_ reference, e.g., the Deref impl on Arc);

  • or with guaranteed uniqueness (&mut _ reference)

    • This is such a strong guarantee, that any kind of mutation is deemed acceptable, including moving data through ::core::mem::swap / core::mem::replace, etc.

The Pin is a wrapper used to restrict the power of the latter case: despite the uniqueness of a Pin<&mut _> reference, mutation is still restricted, to avoid moves on position-sensitive data (!Unpin), such as self-referential structs.

Hence, when having a Box<_>, a unique-owner kind of pointer, &mut _-ness is capable of transitively reaching the pointee (for<T> Box<T> : DerefMut<Target = T>), and thus having Pin<Box<T>> is indeed accurate to prevent movement mishaps.

With Arc<T> (and Rc<T>) however, since there is shared / aliased ownership, there is no impl<T> DerefMut for Arc<T>, and we go back to using the “weak” &T that does not permit moving the pointee.

  • Regarding the get_mut and make_mut methods, the former will always fail as long as at least one Arc remains leaked (i.e., after a call to Arc::into_raw and before the freeing Arc::from_raw), and in a similar fashion the latter will always clone the pointee. Hence the one leaked into FFI is never accessed by a &mut _.

Thus, despite the good intention, what your initial code did seems best. And of course the callback should not mutate the given context, except through Sync-hronisation primitive such as Mutex<_> or RwLock<_> (in which case, by the way, Pin would have been giving a false sense of satefy: since the pinning is not structural in a Mutex/RwLock, Pin<Arc<Mutex<T>>> does never imply that T is pinned).

3 Likes