Random number without using the external crate?

I'm new to Rust and somehow finagled myself into teaching it to some high school students. They can't install anything on their school computers so I'm using Repl.it, which doesn't allow external crates. Is there any way to generate a random number with the standard library?

2 Likes

Not of high quality, but sure. How about allocating something, taking the pointer to it, retreiving the adress, casting that to u32 and then applying modulo operation to get it into the range you need? Won't survive any QA, but probably random enough for just-doing-stuff(tm).

Sorry, I am super new to Rust. What do you mean by "allocating something"? And yeah, this isn't production quality, this is just to work around the guessing game that is done in the rust book.

"Allocating something" means making a variable. It's value will take some space in memory, and that space will have an address, which is not-really-but-something-like-maybe-random.

Here is an example: Rust Playground. You might want to play around a bit, I've had some strange results trying this in a loop, I guess the optimizer was too helpfull.

2 Likes

Thank you! That was very helpful.

That said, you could also switch to the playground and use crates there. Not sure if that fits your bill, though.

2 Likes

Feels super no-go to me, but should do it ^^

You could use the current time with a high precision in the same way, if that's better for you :stuck_out_tongue:

Here is an example that shows how to print a raw pointer (as KillTheMule suggested):

let ptr = Box::into_raw(Box::new(123));
println!("ptr: {}", ptr as usize);

You will notice that the number is always even -- since it is a memory address, it will be aligned to the size of an integer.

An easier way (in my opinion) to make a poor-mans-random-generator is to look at the current time when the program starts. This example find the current time, expresses it as a duration since 1970, and then extracts the number of nanoseconds elapsed in the current second:

use std::time::{SystemTime, UNIX_EPOCH};

fn main() {
    let nanos = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .subsec_nanos();
    println!("nanos: {}", nanos);
}

So if the current time is 10:20:30.123456789, the "random" number will be 123456789. The number will always be between zero and 1 billion.

Your students can then proceed to use a bit of modulo arithmetic to get a random number in the desired range. That should be more than good enough for a guessing game :slight_smile:

7 Likes

I seriously love this solution. I also love any excuse to use more random math.

I hope, a basic RNG will be included in the future. Until then you can use the following implementation.


const KX: u32 = 123456789;
const KY: u32 = 362436069;
const KZ: u32 = 521288629;
const KW: u32 = 88675123;

pub struct Rand {
    x: u32, y: u32, z: u32, w: u32
}

impl Rand{
    pub fn new(seed: u32) -> Rand {
        Rand{
            x: KX^seed, y: KY^seed,
            z: KZ, w: KW
        }
    }

    // Xorshift 128, taken from German Wikipedia
    pub fn rand(&mut self) -> u32 {
        let t = self.x^self.x.wrapping_shl(11);
        self.x = self.y; self.y = self.z; self.z = self.w;
        self.w ^= self.w.wrapping_shr(19)^t^t.wrapping_shr(8);
        return self.w;
    }

    pub fn shuffle<T>(&mut self, a: &mut [T]) {
        if a.len()==0 {return;}
        let mut i = a.len()-1;
        while i>0 {
            let j = (self.rand() as usize)%(i+1);
            a.swap(i,j);
            i-=1;
        }
    }

    pub fn rand_range(&mut self, a: i32, b: i32) -> i32 {
        let m = (b-a+1) as u32;
        return a+(self.rand()%m) as i32;
    }

    pub fn rand_float(&mut self) -> f64 {
        (self.rand() as f64)/(<u32>::max_value() as f64)
    }
}

fn main() {
    let mut rng = Rand::new(0);
    
    // Throw a dice 100 times
    let v: Vec<i32> = (0..100).map(|_| rng.rand_range(1,6)).collect();
    println!("{:?}",v);

    // Shuffle an array
    let mut v: Vec<u32> = (1..101).collect();
    rng.shuffle(&mut v);
    println!("{:?}",v);
}

Nice attempt but you can't generate random (non-deterministic) numbers from a deterministic program

repl.it allows to read from /dev/urandom, so the simplest solution (and correct one) will be to read from it:

use std::fs::File;
use std::io::Read;

fn main() {
  let mut f = File::open("/dev/urandom").unwrap();
  let mut buf = [0u8; 16];
  f.read_exact(&mut buf).unwrap();
  println!("{:?}", buf);
}

UPD: /dev/random -> /dev/urandom

10 Likes

True. This also means that a computer cannot generate a random number in any case.

That's why there is a seed in all algorithms. The current time is often used as a seed because it's "random enough".

The difference in the implementations is usually just in how well-distributed the generated numbers are, and in the performance.

If you need a few random numbers, taking the nanoseconds of the current time is good enough. If you need a lot of them in a tight loop, you could end up in the situation where the same set of numbers comes repeatedly, as the loop body always does the same thing over and over, probably in the same time.

1 Like

This is a good idea, but /dev/random has 2 problems you need to be aware of:
1- it's Linux (maybe Unix) only
2- you could deplete the random numbers pool, and need to wait for it to be filled again by the kernel.

/dev/urandom gives less-random numbers, but has no pool, so you can get all the numbers you want, knowing they could be not random at all.

I agree. The reason i pointed out is that since he is taking the seed as a parameter to a public function, if you pass the same seed you will get the same number right?

Well, if no one else is going to, I suppose I will:

fn get_random_number() -> i32 {
    4 // chosen by fair dice roll.
      // guaranteed to be random.
}

(RFC 1149.5 specifies 4 as the standard IEEE-vetted random number.)


(You could also use a PCG, which is pretty simple.)

5 Likes

The seed is what defines the start of the sequence. Every time you request a new number from the RNG created with that seed you get a different one, but the sequence is always the same.

This allows you to test the RNG: By passing a pre-defined seed you can check that the sequence is always the same. You can also check with different seeds if the distribution is flat or has spikes.

Incidentally, this also allows to "reverse-check" a code: let's say you generate a certain number of codes starting from a seed. If you receive the code and you somehow know the seed used and the number of codes generated with that seed, you can know if the code belongs to that seed's sequence, therefore validating it.

By passing a semi-random number (like the current timestamp) to a RNG at runtime, you get a random sequence. As random as a computer can generate, naturally. :slight_smile:

This thread made me have some fun thinking about how to generate randomish numbers. "Ask the user to roll a dice and type the result" was one (but I knew that RFC before, so it wasn't very creative :wink: )

I asked in another group I'm in and one of my friends recommended this method, which uses C's rand function:

fn main() {
  unsafe {
    srand();
    println!("{}", rand());
  }
}

extern "C" {
  fn srand() -> u32;
  fn rand() -> u32;
}

I'm going to use the nanoseconds to generate time since that introduces the least amount of new concepts, but wanted to post this as another option in case anyone else runs into this issue!

1 Like