While modifying my code to run in into parallel, I came across this pattern of solution to pass a pointer to some structure to each thread. Each thread only access its index in the vector, so they never share the random number generation structure.
This code compiles fine because I say to the borrow checker to trust me.
use rand_mt::{Mt19937GenRand64, Mt64};
const NTHREADS: usize = 2;
const SEED: u64 = 14;
fn main() {
let mut rng = Vec::new();
let mut rngs = Vec::new();
for i in 0..NTHREADS {
rng.push(Mt64::new(SEED + i as u64));
let rng_ptr: *mut Mt19937GenRand64 = &mut rng[i] as *mut _;
rngs.push(unsafe{&mut *rng_ptr});
}
let rand: f64 = rngs[0].gen();
}
But this leads to use-after-free, and I wonder why. Shouldn't the rng
vector live long enough that it is not deallocated? The weirdest part is that it does not lead to use after free for NTHREADS=1
.
First of all, this is undefined behavior regardless of what the program does whenever NTHREADS
is more than 1. rng.push
and rng[i]
both create mutable references to the entire rng
, which invalidates all existing references stored in rngs
.
Your program encounters use-after-free when run because rng
is reallocated when the second element is added. Vec
in the current standard library allocates space for one element on the first push
, then allocates space for two elements and deallocates the old region on the second push
. Trying to use the pointer to the old region is a use-after-free.
One way to fix this is to loop over rng
after all the elements have been added:
let rngs: Vec<&mut Mt64> = rng.iter_mut().collect();