Memory being dropped out of order in 'a

I’m trying to write a type that is sort of like Cow<[X]>. It is called A. A looks like this minimal example:

struct A<'a, X> {
    ptr: NonNull<X>,
    len: usize,
    own: bool,
    _pd: PhantomData<&'a X>,
}

impl<'a, X> A<'a, X> {
    pub fn new(len: usize) -> A<'a, X> {
        let lay = Layout::array::<X>(len).unwrap();
        let ptr = unsafe { alloc::alloc(lay) };
        let ptr = NonNull::new(ptr as *mut X).unwrap();
        A {
            ptr,
            len,
            own: true,
            _pd: PhantomData,
        }
    }

    pub fn set(&mut self, i: usize, x: X) {
        unsafe {
            write(self.ptr.as_ptr().add(i), x);
        }
    }

    pub fn get(&self, i: usize) -> X {
        unsafe { read(self.ptr.as_ptr().add(i)) }
    }

    pub fn as_ref(&self) -> A<'a, X> {
        A {
            ptr: self.ptr,
            len: self.len,
            own: false,
            _pd: PhantomData,
        }
    }
}

impl<'a, X> Drop for A<'a, X> {
    fn drop(&mut self) {
        if self.own {
            unsafe {
                alloc::dealloc(
                    self.ptr.as_ptr() as *mut u8,
                    Layout::array::<X>(self.len).unwrap(),
                );
            }
        }
    }
}

if i split up assignment, this code works as expected

fn main() {
    let mut a: A<i32> = A::new(3);
    a.set(0, 1);
    a.set(1, 2);
    a.set(2, 3);

    for i in 0..3 {
        println!("{i}:\t{}", a.get(i));
    }

    let mut b: A<i32> = a.as_ref();
    b.set(0, 1);
    b.set(1, 2);
    b.set(2, 3);

    for i in 0..3 {
        println!("{i}\t{}", b.get(i));
    }
}

but creating references inline causes the original A to be dropped before the ref, like this

fn main() {
    let mut a: A<i32> = A::new(3).as_ref();
    a.set(0, 1);
    a.set(1, 2);
    a.set(2, 3);

    for i in 0..3 {
        println!("{i}:\t{}", a.get(i));
    }
}

which yields an error in miri

  --> bin/drop.rs:30:13
   |
30 |             write(self.ptr.as_ptr().add(i), x);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here
   |
   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
help: alloc392 was allocated here:
  --> bin/drop.rs:18:28
   |
18 |         let ptr = unsafe { alloc::alloc(lay) };
   |                            ^^^^^^^^^^^^^^^^^
help: alloc392 was deallocated here:
  --> bin/drop.rs:52:17
   |
52 | /                 alloc::dealloc(
53 | |                     self.ptr.as_ptr() as *mut u8,
54 | |                     Layout::array::<X>(self.len).unwrap(),
55 | |                 );
   | |_________________^
   = note: BACKTRACE (of the first span):
   = note: inside `A::<'_, i32>::set` at bin/drop.rs:30:13: 30:47
note: inside `main`
  --> bin/drop.rs:63:5
   |
63 |     a.set(0, 1);
   |     ^^^^^^^^^^^

does anyone know what im doing wrong here?

If you do let mut a: A<i32> = A::new(3).as_ref(), the 'a will be inferred as 'static and A::new(3) temporary gets dropped at the end of the statement. You have to tie the 'a returned by as_ref to the 'elided lifetime of &'elided self using for example pub fn as_ref<'b>(&'b self) -> A<'a, X> where 'b: 'a (or 'a: 'b, never sure what order I need to use them in) to ensure A::new(3).as_ref() is an error.

1 Like