The best way to genearate random values in multiple places in the code (rand crate)

Hi,
I have a project where I need to generate random values multiple times and in different sections (different functions) in the code using rand crate. So, is it better to make "ThreadRng" a member of the struct and use it when necessary using "self" like the following code, or for each function I should define new "ThreadRng" by declaring "let mut rng = rand::thread_rng();" ??

use rand::distributions::{Distribution, Uniform};
struct MyStruct {    
    rng : ThreadRng,   
}

impl MyStruct {
    // first function uses rng
    fn f1(&mut self)->Vec<f64>{
        let _range = Uniform::from(0.0f64..=1.0f64);

         // or for each function I hould define rng using the following instruction ?   
        // let mut rng = rand::thread_rng();   

        let mut rand_vec : Vec<f64> = Vec::with_capacity(10);
        for i in 0..10 {
            rand_vec.push(_range.sample(&mut self.rng));
        }
        rand_vec
    } 

    // second function uses rng
    fn f2(&mut self)->Vec<f64>{
        let _range = Uniform::from(-100.0f64..=100.0f64);

         // or for each function I hould define rng using the following instruction ?   
        // let mut rng = rand::thread_rng();   

        let mut rand_vec : Vec<f64> = Vec::with_capacity(10);
        for i in 0..10 {
            rand_vec.push(_range.sample(&mut self.rng));
        }
        rand_vec
    }
}
1 Like

rand::thread_rng() already caches the ThreadRng object after the first call and retrieves it on later calls, so storing it yourself makes no difference.

1 Like

I am not sure if this is the best... But, in my Rendering lib, I am using SmallRng (I think it is not good for cryptographic applications) and keeping it independently. The reason I do this is because:

  • I need several of them—one per thread—and they cannot be correlated (using the same one created weird artefacts)
  • I found that storing them in MyStruct can be painful because that means that any method that produces a random number needs to use &mut self, which I find kind nonsense (unsafe?).

Let me explain the second point:

// Doing this 
struct Material {
     reflectivity: f64,
     rng: SmallRng 
}

// Implies that this method receives a mutable reference to Self, 
// EVEN IF THE PROPERTIES OF THE MATERIAL DO NOT CHANGE 
impl Material{
     fn reflect(&mut self, ray_in: &Ray)->{
            // .... this is not actual code.
            let (new_x, new_y, new_z) : (f64, f64, f64) = self.rng.gen();
            // return a random vector
            Vector3D::new(new_x, new_y, new_z)
     }
}

On the contrary:

// Doing this 
struct Material {
     reflectivity: f64
}

// Means that I can call the same method with an immutable reference to Self.
// THIS ALSO MEANS THAT I CAN PASS THE MATERIAL OBJECT TO DIFFERENT 
// THREADS SAFELY!
impl Material {
     fn reflect(&self, ray_in: &Ray, rng: &mut SmallRng)->{
            // .... this is not actual code.
            let (new_x, new_y, new_z) : (f64, f64, f64) = rng.gen();
            // return a random vector
            Vector3D::new(new_x, new_y, new_z)
     }
}

This is my view, at least.... I guess it will really depend on the kind of application you targeting. If you were coding a set of gambling machines, or a set of random-walking entities, maybe it makes sense to make them mutable (e.g., fn walk(&mut self){ self.position += self.rng.gen(); }).

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.