Background and motivation
I'm a C++ developer with a passion for modern, multi-threaded and functional code. I came across Rust not so long ago and it sounded like everything I wanted my C++ code to be. Naturally, I decided to try it out.
I was working on my own C++ project that had a need for some random numbers. I thought it would be a good opportunity to use some Rust and call into it from my C++ code. After some messing about, I thought I would convert some C/C++ code to Rust from the following pages:
Long story short, I ended up with the following code:
Code
#[macro_use]
extern crate lazy_static;
mod pointgen_random
{
const CMWC_CYCLE: u32 = 4096;
const PHI: u32 = 0x9e3779b9;
struct ComplementaryMultiplyWithCarryGen {
q: [u32; CMWC_CYCLE as usize],
c: u32,
i: usize
}
impl ComplementaryMultiplyWithCarryGen {
fn new(seed: u32) -> ComplementaryMultiplyWithCarryGen {
let mut q_init = [0; CMWC_CYCLE as usize];
q_init[0] = seed;
q_init[1] = seed + PHI;
q_init[2] = seed + PHI + PHI;
for i in 3..CMWC_CYCLE as usize{
q_init[i] = q_init[i - 3] ^ q_init[i - 2] ^ PHI ^ seed;
}
return ComplementaryMultiplyWithCarryGen{q: q_init, c: 362436, i: 4095};
}
fn reset(&mut self, seed: u32){
let cmwc = ComplementaryMultiplyWithCarryGen::new(seed);
self.q = cmwc.q;
self.i = cmwc.i;
self.c = cmwc.c;
}
fn random(&mut self) -> u32{
const A: u64 = 18782;
const R: u32 = 0xfffffffe;
self.i = (self.i + 1) & (CMWC_CYCLE as usize - 1);
let t = A * self.q[self.i] as u64 + self.c as u64;
self.c = (t >> 32) as u32;
let mut x = (t + self.c as u64) as u32;
if x < self.c {
x+=1;
self.c+=1;
}
self.q[self.i] = R - x;
return self.q[self.i];
}
}
use std::sync::Mutex;
// Lazy singleton random number generator
lazy_static!{
static ref GLOBAL_CMWC_GEN: Mutex<ComplementaryMultiplyWithCarryGen> = Mutex::new(ComplementaryMultiplyWithCarryGen::new(0));
}
pub fn get_random_cmwc() -> u32
{
GLOBAL_CMWC_GEN.lock().unwrap().random()
}
pub fn set_cmwc_seed(seed: u32)
{
GLOBAL_CMWC_GEN.lock().unwrap().reset(seed);
}
}
// Public exports for PointGenLib
// All prefixed with pointgen_ to avoid name conflicts
#[no_mangle]
pub extern "C" fn pointgen_set_cmwc_seed(seed: u32)
{
pointgen_random::set_cmwc_seed(seed);
}
#[no_mangle]
pub extern "C" fn pointgen_random_cmwc() -> u32
{
return pointgen_random::get_random_cmwc();
}
It compiles on Windows without warning and appears to give some sensible pseudo-random numbers from 0 to 2^32, but there's just a lot of things in it that I don't like or don't feel right:
- In
fn new(), havingq_initbemutseems like a hack, is there a more functional way to do this using slices? - Is this an appropriate struct to use the
Defaulttrait? I read that it's not recommended to use that. - There's a lot of
as usizearound, I don't quite have the feel for casting types as I would do in C++. fn reset()seems ridiculous, is there a better way to reassign all values at once?
Any other help/suggestions/advice you can give would be very much appreciated.