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])
// };
}
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.
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.