Is a Box allocation stable?

Is this code safe? If not, how to fix?

I'd also appreciate pointers to appropriate documentation describing why this is safe (or not), the documentation of Box is somewhat sparse in this regard.

use std::cell::Cell;
struct Storage {
    boxes: Cell<Vec<std::boxed::Box<[u8]>>>,
}
impl Storage {
    fn new() -> Storage { Storage { boxes: Cell::new(vec![]) } }
    fn add<'x>(&'x self, data: &[u8]) -> &'x [u8] {
        let mut boxes = self.boxes.take();
        boxes.push(data.to_vec().into_boxed_slice());
        boxes.reserve(boxes.len() + 100);
        let slice: &[u8] = &boxes[boxes.len() - 1];
        // Is this safe?
        let res = unsafe { std::mem::transmute::<&[u8], &'x [u8]>(slice) };
        self.boxes.set(boxes);
        res
    }
}
fn main() {
    let s = Storage::new();
    let x1 = s.add(&[1, 2, 3, 1, 2, 2, 21, 1, 2, 1]);
    let x2 = s.add(&[4, 5, 6]);
    println!("{:?}", x1);
    println!("{:?}", x2);
    let x3 = s.add(&[1, 2, 3]);
    let x4 = s.add(&[4, 5, 6]);
    println!("{:?}", x3);
    println!("{:?}", x4);
    // Should not compile:
    // let x = {
    //     let s = Storage::new();
    //     s.add(&[1, 2, 3])
    // };
}

It might be UB due to What are the uniqueness guarantees of Box and Vec? · Issue #326 · rust-lang/unsafe-code-guidelines · GitHub. While this is prevalent in the ecosystem, it's better to avoid it. Use raw pointers instead or crates that emulate Box.

TLDR, due to the reserve with + 100, this code's memory safety depends on the details of Vec, not Box.

A Vec<T> can be aliased (and Ralf indicated that this is very, very, very likely not going to change due to ecosystem usage). That is, moving a Vec<T> does not have the side effect of acting as though the Ts inside it are moved, invalidating references to them (for the purposes of provenance and the aliasing model). Moving a Box<T> would, currently, act as though its pointee is moved (by invalidating other pointers to the Box's contents), but since you move a Vec<Box<[u8]>>, the Box<[u8]>s aren't moved / don't newly assert their noalias.

However, Storage::add is obviously unsound, since the Vec would eventually reallocate and move the Box<[u8]>s, which would then trigger the noalias issues.

Thanks. So a possible fix is to use Vec<Vec<u8>> instead of Vec<Box<[u8]>>?

That was the information I was missing. How exactly can I derive this fact from the documentation? (Also, isn't Miri supposed to flag such code?)

use std::cell::Cell;
struct Storage {
    vecs: Cell<Vec<Vec<u8>>>,
}
impl Storage {
    fn new() -> Storage { Storage { vecs: Cell::new(vec![]) } }
    fn add<'x>(&'x self, data: &[u8]) -> &'x [u8] {
        let mut vecs = self.vecs.take();
        vecs.push(data.to_vec());
        vecs.reserve(vecs.len() + 100);
        let slice: &[u8] = &vecs[vecs.len() - 1];
        // Is this safe?
        let res = unsafe { std::mem::transmute::<&[u8], &'x [u8]>(slice) };
        self.vecs.set(vecs);
        res
    }
}

Following a warning that this information is not set in stone: "The aliasing rules for Box<T> are the same as for &mut T. Box<T> asserts uniqueness over its content. Using raw pointers derived from a box after that box has been mutated through, moved or borrowed as &mut T is not allowed."

It doesn't say that moving a Box<T> sort of semantically moves its pointee, that's just something I figured out.

It can. It's easy to come with an example that fails under Stacked Borrows but passes under Tree Borrows. Try running the following playground through Miri: Rust Playground

I don't know off the top of my head if there's a noalias violation that fails under Tree Borrows.

If you're willing to accept the extra size_of::<usize>() bytes per slice, then yes. You could use a wrapper around NonNull<[u8]> instead to keep it at 2 pointers in size.

Thanks!

Vec with raw pointers (Box::into_raw()) and a impl Drop for Storage?