Using thread-local!
(as suggested in another answer) to solve this was not that straightforward, so I include here a full solution:
use std::cell::RefCell;
use std::collections::HashMap;
fn main() {
println!("thread-local demo for Collatz:");
(2..20).for_each(|n| println!("{n}: {c}", n = n, c = collatz(n)));
}
thread_local! (static COLLATZ_CACHE: RefCell<HashMap<usize, usize>> = {
let mut cache = HashMap::new();
cache.insert(1, 0);
RefCell::new(cache)
});
fn collatz(n: usize) -> usize {
COLLATZ_CACHE.with(|cache| {
let entry = cache.borrow().get(&n).copied();
if let Some(v) = entry { v } else {
let v = match n % 2 {
0 => 1 + collatz(n / 2),
1 => 1 + collatz(n * 3 + 1),
_ => unreachable!(),
};
cache.borrow_mut().insert(n, v);
*cache.borrow().get(&n).unwrap()
}
})
}
If thread-local storage is not global enough, then you can use the functionality of the once_cell crate -- which is also on its way into std (already in nightly) -- to initialize a static variable:
#![feature(once_cell)]
use std::collections::HashMap;
use std::lazy::SyncLazy;
use std::sync::Mutex;
fn main() {
println!("once_cell demo for Collatz:");
(2..20).for_each(|n| println!("{n}: {c}", n = n, c = collatz(n)));
}
static COLLATZ_CACHE: SyncLazy<Mutex<HashMap<usize, usize>>> = SyncLazy::new(|| {
let mut cache = HashMap::new();
cache.insert(1, 0);
Mutex::new(cache)
});
fn collatz(n: usize) -> usize {
let cache = &COLLATZ_CACHE;
let entry = cache.lock().unwrap().get(&n).copied();
if let Some(v) = entry {
v
} else {
let v = match n % 2 {
0 => 1 + collatz(n / 2),
1 => 1 + collatz(n * 3 + 1),
_ => unreachable!(),
};
cache.lock().unwrap().insert(n, v);
*cache.lock().unwrap().get(&n).unwrap()
}
}
playground