let mut v: Vec<&str> = Vec::new();
loop {
let data = String::from("Hello, world!");
// Fill the vector with some data bound to the scope of the loop.
v.push(data.as_str());
// Do things with the data.
for s in &v { /* ... */ }
// Clear the vector, ensuring there is no references left in the vector.
v.clear();
}
This obviously does not compile because there is lots of ways for those references to leak outside of the loop. The main one being something panicking, or v.clear() not being called.
However, it seems reasonable enough to want to reuse the allocation of v in subsequent iterations of the loop.
This topic basically cover this exact case, but don't really like the solution proposed there.
I think its easier to solve this problem by using something like a VecAlloc.
// `Option` ensures that if something goes wrong in the loop, there won't be a double-free.
let mut valloc: Option<VecAlloc> = Some(VecAlloc::from_vec(Vec::new()));
loop {
let v = valloc.take().unwrap().into_vec::<&str>();
// Do things
v = Some(VecAlloc::from_vec(v));
}
It might be of interest that it's possible to recycle a Vec without any unsafe code at all, by taking advantage of vec.into_iter().collect() reusing the backing storage. Here's a toy example of doing this to enable reusing storage for borrows with different lifetimes and different types too:
use std::mem::size_of;
use std::path::{Path, PathBuf};
struct ReusableBuffer<B> {
vec: Option<Vec<B>>,
}
impl<B> ReusableBuffer<B> {
pub fn new() -> Self {
Self { vec: None }
}
pub fn take<T>(&mut self) -> Vec<T> {
// Not for safety, only to ensure the expected performance characteristic.
// Strictly we should check the alignment too.
debug_assert!(size_of::<B>() == size_of::<T>());
let mut vec = self.vec.take().unwrap_or_else(Vec::new);
vec.clear();
let converted: Vec<T> =
vec.into_iter().map(|_| unreachable!()).collect();
converted
}
pub fn put<T>(&mut self, mut returned: Vec<T>) {
debug_assert!(size_of::<B>() == size_of::<T>());
returned.clear();
self.vec = Some(returned.into_iter().map(|_| unreachable!()).collect());
}
}
fn main() {
let mut b = ReusableBuffer::<&str>::new();
{
let s = String::from("foo");
let mut v: Vec<&str> = b.take();
v.push(&*s);
println!("{:p}, {:?}", &*v, v);
b.put(v);
}
{
let p = PathBuf::from("/bar");
let mut v: Vec<&Path> = b.take();
v.push(&*p);
println!("{:p}, {:?}", &*v, v);
b.put(v);
}
}
This doesn't have any particular advantage over the crates linked above, other than relying solely on std's unsafe rather than additional unsafe.
An ad-hoc implementation of the approach @kpreid mentioned to your example code looks like this
let mut empty_v: Vec<&str> = Vec::new();
loop {
let mut v = empty_v;
let data = String::from("Hello, world!");
// Fill the vector with some data bound to the scope of the loop.
v.push(data.as_str());
// sanity check: address stays the same
println!("{:p}", &v[..]);
// Do things with the data.
for s in &v { /* ... */ }
// Clear the vector, ensuring there is no references left in the vector.
v.clear();
empty_v = v.into_iter().map(|_| unreachable!()).collect();
}
(adapted from @kpreid's solution above as well as the code in a previous post of mine here)
Note that (as explained in the other post and subsequent discussion) you can skip the into_iter+collect step on the take side due to variance.