Reputation: 652
I'm trying to write a container for objects of type T
which provides access to references &T
to the stored objects (I want to avoid making copies). Since the container only ever grows during its lifetime, the lifetime of the returned references &T
should be the same as for the container.
The closest I got so far was to use Box<T>
objects internally in my container and use Box<T>.as_ref()
to return references to these objects. Then, however, I run into the same problem as in this minimal example:
fn main() {
let mut v = vec![Box::new(1)]; // line 1
let x = v[0].as_ref(); // line 2: immutable borrow occurs here
println!("x = {:?}", x); // line 3
v.push(Box::new(2)); // line 4: mutable borrow occurs here -> error
println!("x = {:?}", x); // line 5
}
I understand that it would be unsound to use x
in line 5 if it had been deleted from v
during the the mutable borrow. But that's not the case here, and it'll never be for my container. If there's no safe way to express this in Rust, how could I "repair" the example (without copying x
)?
Upvotes: 2
Views: 1230
Reputation: 31263
Unfortunately there's nothing "ready to use" for your usecase, but you can write a wrapper around Vec
that does unsafe stuff to give you the features you need while keeping the guarantees that Rust needs
struct BoxVec<T>(Vec<Box<T>>);
impl<T> BoxVec<T> {
pub fn new() -> Self { BoxVec(Vec::new()) }
pub fn push<'a>(&'a mut self, t: T) -> &'a mut T {
let mut b = Box::new(t);
let t: &'a mut T = unsafe { std::mem::transmute::<&mut T, &'a mut T>(&mut b) };
self.0.push(b);
t
}
pub fn pusher<'a>(&'a mut self) -> BoxVecPusher<'a, T> {
BoxVecPusher(self)
}
}
struct BoxVecPusher<'a, T: 'a>(&'a mut BoxVec<T>);
impl<'a, T> BoxVecPusher<'a, T> {
fn push<'b>(&'b mut self, t: T) -> &'a mut T {
let mut b = Box::new(t);
let t: &'a mut T = unsafe { std::mem::transmute::<&mut T, &'a mut T>(&mut b) };
(self.0).0.push(b);
t
}
}
The guarantees I chose is that you can't index into the Pusher
, but you get a mutable reference to the newly pushed object. Once to release the pusher, you can go back to indexing.
An example usage is
let mut v = BoxVec::new();
v.push(1);
let x = v[0];
let mut p = v.pusher();
let i = p.push(2);
p.push(3); // works now
let y = v[0]; // denied by borrow checker
Full example in the playground
Upvotes: 2
Reputation: 14051
As @ker's answer says, just because you only grow a container doesn't mean references stay valid, as the memory can be reallocated.
Another solution if you're only growing the container is to just store indices instead of references:
fn main() {
let mut v = vec!["Foo"]; // line 1
let x = 0; // line 2: just store an index.
println!("x = {:?}", v[x]); // Use the index as needed
v.push("bar"); // line 4: No problem, there are no references.
println!("x = {:?}", v[x]); // line 5: use the index again.
}
Upvotes: 5
Reputation: 31263
The problem is that your Vec
might run out of memory. If that happens, it has to allocate a new (bigger) chunk of memory, copy all the old data over, and delete the old memory allocation. At that point all references into the old container would become invalid.
You could design a rope (a Vec
of Vec
), where, once an inner vec is full, you just create a new one, thus never invalidating pointers just by pushing.
That would require some unsafe code and very careful analysis of the borrowing rules that you have to keep up.
Upvotes: 2