Help with unsound code

Hi,

I'm trying to create an API that looks like this:

    pub fn boxed(count: usize) -> AllocationMut<T> where T: Unpin {
        let boxed_array = vec![T::default(); count].into_boxed_slice();

        let mut pinned = Box::into_pin(boxed_array);
        // My guess is that this slice doesn't live as long as it needs to?
        let slice_mut = Pin::get_mut(pinned.as_mut());
        let memory= NonNull::from(slice_mut);

        AllocationMut {
            memory,
            context: Some(AllocationContext::Boxed(pinned)),
        }
    }

but I can't figure out what's the right way to create a reference that stays valid for as long as the object is alive (not sure if I worded it in an understandable way). Miri rightfully complains that the pointer is trying to make a reference that doesn't exist.

I've set up a playground that shows the issue.

What's the right way to make this work?

Don't bother, it does not offer anything of value. (Makes anyone reading the code have to learn what it achieves.)

If you must do something then a structure with a pointer (into_raw) and count + associated functions.

Not sure what you're saying. The current structure works already with arena allocations, now I wanna be able to create an AllocationMut out of a Box, mostly as an utility for tests.

This is a previous thread, for context: Unsound arena implementation

Box<T> is currently defined to be a unique pointer, just like &mut T. This means that if you borrow it and then move the Box, that move invalidates the borrow. (And no, you can’t get around this by using raw pointers and avoiding references; it’s the Box pointer itself that prohibits aliasing, regardless of what other pointer types are used.) There are cases related to pinning where this is not true, but those are not guaranteed (there is no stable API to ask for that, yet).

For now, you must use something like AliasableBox instead of Box (or do your own allocation). In the future, there may be better options.

Also, there is no value in involving Pin here. Pin is only useful to make contracts between two separately-written pieces of unsafe code; if your public API does not expose any Pins and does not allow T: !Unpin, then there is no point in using Pin inside your module.

2 Likes

Isn't this what Pin is for? To prevent the Box from moving? Anyway, after reading the docs of the Aliasable crate, I think I understand what you're saying. I'll incorporate the changes and see what happens :slight_smile:

Not at all!

For some pointer type Ptr<T>, Pin<Ptr<T>> means that the T will not move, not that the Ptr<T> will not move. Pin<Box<T>> owns Box<T> directly, so moving Pin<Box<T>> (as you are doing) is moving Box<T> too.

And because you have T: Unpin, it doesn’t even mean that — Unpin re-enables moving of the value. Pin + T: Unpin does nothing; it exists only so that types which don't need pinning can participate in Pin-using generic code without taking on all of the Pin requirements.

1 Like

Hmm, it seems there's no way to construct a NonNull out of an AliasableBox?

Interesting. I admit I haven't actually used aliasable myself — perhaps we’re both missing something. If not, your best choice may be to manage the allocation yourself — define your own type to own the pointer and deallocate on drop.

Aha, I figured it out. It looks like it works now, at least miri doesn't complain anymore!

    pub fn boxed(count: usize) -> AllocationMut<T> {
        let boxed_slice = vec![T::default(); count].into_boxed_slice();

        let mut aliasable_box = AliasableBox::from(boxed_slice);

        let data = aliasable_box.as_mut();

        let memory = unsafe { NonNull::new_unchecked(core::ptr::from_mut(data)) };

        AllocationMut {
            memory,
            context: Some(AllocationContext::Boxed(aliasable_box)),
        }
    }

Thanks for your help again!