Loading page…
Rust walkthroughs
Loading page…
rand::rngs::SmallRng optimize for speed over cryptographic security?rand::rngs::SmallRng is a non-cryptographic pseudo-random number generator optimized for speed and small memory footprint, designed for simulations, games, and other applications where cryptographic security is unnecessary. Unlike StdRng which uses ChaCha20 for cryptographic security, SmallRng typically implements Xoshiro256PlusPlus or similar algorithms that sacrifice cryptographic guarantees for raw throughput—often 5-10x faster than cryptographic alternatives. The trade-off is that SmallRng output is predictable given sufficient observed values, making it unsuitable for security-sensitive contexts like key generation, password tokens, or any scenario where an attacker might predict future outputs from past observations.
use rand::{SeedableRng, Rng};
use rand::rngs::SmallRng;
fn main() {
// Create from entropy (thread_rng seed)
let mut rng = SmallRng::from_entropy();
// Generate various types
let random_u32: u32 = rng.gen();
let random_f64: f64 = rng.gen_range(0.0..1.0);
let random_idx: usize = rng.gen_range(0..100);
println!("u32: {}", random_u32);
println!("f64: {}", random_f64);
println!("idx: {}", random_idx);
}SmallRng is created from entropy or a seed and generates random values through the Rng trait.
use rand::rngs::{SmallRng, StdRng};
use std::mem::size_of;
fn main() {
// SmallRng: typically 32 bytes (Xoshiro256 state)
println!("SmallRng size: {} bytes", size_of::<SmallRng>());
// StdRng: typically 112 bytes (ChaCha state)
println!("StdRng size: {} bytes", size_of::<StdRng>());
// Memory footprint comparison
let ratio = size_of::<StdRng>() as f64 / size_of::<SmallRng>() as f64;
println!("StdRng is {:.1}x larger", ratio);
}SmallRng has significantly smaller state, reducing memory overhead and improving cache efficiency.
// SmallRng typically uses Xoshiro256++ or similar
// Characteristics:
// - State: 256 bits (32 bytes)
// - Period: 2^256 - 1
// - Speed: Very fast
// - Security: NOT cryptographically secure
// StdRng uses ChaCha20
// Characteristics:
// - State: Larger (ChaCha state + counter)
// - Period: Effectively infinite
// - Speed: Slower due to crypto operations
// - Security: Cryptographically secure
use rand::{SeedableRng, Rng};
use rand::rngs::{SmallRng, StdRng};
use std::time::Instant;
fn benchmark_rng<R: Rng + SeedableRng>(name: &str, iterations: u64) {
let mut rng = R::from_entropy();
let start = Instant::now();
let mut sum: u64 = 0;
for _ in 0..iterations {
sum = sum.wrapping_add(rng.gen::<u64>());
}
let elapsed = start.elapsed();
let ns_per_gen = elapsed.as_nanos() as f64 / iterations as f64;
let gens_per_sec = iterations as f64 / elapsed.as_secs_f64();
println!("{}:", name);
println!(" Time: {:?}", elapsed);
println!(" ns/gen: {:.2}", ns_per_gen);
println!(" gens/sec: {:.0}", gens_per_sec);
std::mem::forget(sum); // Prevent optimization
}
fn main() {
let iterations = 10_000_000;
benchmark_rng::<SmallRng>("SmallRng", iterations);
benchmark_rng::<StdRng>("StdRng", iterations);
}The algorithm choice directly impacts performance characteristics.
use rand::{SeedableRng, Rng};
use rand::rngs::{SmallRng, StdRng};
use std::time::Instant;
fn main() {
const COUNT: usize = 1_000_000;
// Benchmark u64 generation
let mut small_rng = SmallRng::from_entropy();
let start = Instant::now();
for _ in 0..COUNT {
let _: u64 = small_rng.gen();
}
let small_time = start.elapsed();
let mut std_rng = StdRng::from_entropy();
let start = Instant::now();
for _ in 0..COUNT {
let _: u64 = std_rng.gen();
}
let std_time = start.elapsed();
println!("SmallRng: {:?}", small_time);
println!("StdRng: {:?}", std_time);
println!("Speedup: {:.1}x", std_time.as_secs_f64() / small_time.as_secs_f64());
// Benchmark range generation (more complex)
let mut small_rng = SmallRng::from_entropy();
let start = Instant::now();
for _ in 0..COUNT {
let _: u32 = small_rng.gen_range(0..1000);
}
let small_range_time = start.elapsed();
let mut std_rng = StdRng::from_entropy();
let start = Instant::now();
for _ in 0..COUNT {
let _: u32 = std_rng.gen_range(0..1000);
}
let std_range_time = start.elapsed();
println!("\nRange generation:");
println!("SmallRng: {:?}", small_range_time);
println!("StdRng: {:?}", std_range_time);
}SmallRng typically achieves higher throughput for both raw value and range generation.
use rand::{SeedableRng, Rng};
use rand::rngs::SmallRng;
fn main() {
let mut rng = SmallRng::from_entropy();
// Statistical tests show SmallRng passes standard test suites
// (PractRand, TestU01, etc.)
// Uniformity check
const BUCKETS: usize = 100;
const SAMPLES: usize = 1_000_000;
let mut counts = [0usize; BUCKETS];
for _ in 0..SAMPLES {
let bucket = rng.gen_range(0..BUCKETS);
counts[bucket] += 1;
}
let expected = SAMPLES / BUCKETS;
let max_deviation = counts.iter()
.map(|&c| (c as i64 - expected as i64).abs())
.max()
.unwrap();
println!("Uniformity test:");
println!(" Expected per bucket: {}", expected);
println!(" Max deviation: {} ({:.2}%)",
max_deviation,
100.0 * max_deviation as f64 / expected as f64
);
// SmallRng is statistically sound for simulation
// just not cryptographically secure
}SmallRng provides good statistical properties for simulation despite lacking cryptographic security.
use rand::{SeedableRng, RngCore};
use rand::rngs::SmallRng;
fn main() {
// SmallRng typically uses Xoshiro256++
// which has:
// 1. Very simple state update (just a few XORs and rotations)
// 2. Minimal state (256 bits = 4 u64 values)
// 3. No cryptographic mixing
let mut rng = SmallRng::from_entropy();
// Each call advances state with minimal operations:
// - 3-4 XOR operations
// - 2-3 rotations/shifts
// - One addition
// Contrast with ChaCha20 (StdRng):
// - 20 rounds of mixing
// - Each round: multiple ADD, XOR, ROTATE
// - Quarter-round function on 4 values
// The algorithmic simplicity is why SmallRng is faster
let mut bytes = [0u8; 32];
rng.fill_bytes(&mut bytes);
println!("Bytes: {:?}", bytes);
let next_u64: u64 = rng.gen();
println!("u64: {}", next_u64);
}The speed comes from algorithmic simplicity: fewer operations per generated value.
use rand::{SeedableRng, Rng};
use rand::rngs::SmallRng;
#[derive(Debug)]
struct Enemy {
x: f32,
y: f32,
health: u32,
}
fn spawn_enemies(rng: &mut SmallRng, count: usize) -> Vec<Enemy> {
(0..count)
.map(|_| Enemy {
x: rng.gen_range(0.0..100.0),
y: rng.gen_range(0.0..100.0),
health: rng.gen_range(50..150),
})
.collect()
}
fn simulate_damage(rng: &mut SmallRng, enemies: &mut [Enemy]) {
for enemy in enemies {
// Critical hit chance: 10%
if rng.gen_bool(0.1) {
enemy.health = enemy.health.saturating_sub(50);
} else {
enemy.health = enemy.health.saturating_sub(10);
}
}
}
fn main() {
// Seeded for reproducibility (game replays, etc.)
let mut rng = SmallRng::seed_from_u64(42);
let mut enemies = spawn_enemies(&mut rng, 100);
println!("Spawned {} enemies", enemies.len());
// Simulate combat
for _ in 0..100 {
simulate_damage(&mut rng, &mut enemies);
}
// Fast enough for real-time game loops
// Deterministic when seeded (useful for testing)
}Games benefit from SmallRng's speed and reproducibility with seeds.
use rand::{SeedableRng, Rng};
use rand::rngs::SmallRng;
fn monte_carlo_pi(samples: usize) -> f64 {
let mut rng = SmallRng::from_entropy();
let mut inside = 0;
for _ in 0..samples {
let x: f64 = rng.gen_range(-1.0..1.0);
let y: f64 = rng.gen_range(-1.0..1.0);
if x * x + y * y <= 1.0 {
inside += 1;
}
}
4.0 * inside as f64 / samples as f64
}
fn main() {
let samples = 10_000_000;
let start = std::time::Instant::now();
let pi_estimate = monte_carlo_pi(samples);
let elapsed = start.elapsed();
println!("π estimate: {:.6}", pi_estimate);
println!("Samples: {}", samples);
println!("Time: {:?}", elapsed);
println!("Samples/sec: {:.0}", samples as f64 / elapsed.as_secs_f64());
// Monte Carlo needs many samples quickly
// Cryptographic security is irrelevant
}Monte Carlo simulations need fast random numbers with good statistical properties.
use rand::{SeedableRng, Rng};
use rand::rngs::SmallRng;
fn generate_dungeon(rng: &mut SmallRng, width: usize, height: usize) -> Vec<Vec<char>> {
let mut dungeon = vec![vec!['#'; width]; height];
// Carve rooms
let room_count = rng.gen_range(5..10);
for _ in 0..room_count {
let room_w = rng.gen_range(3..8);
let room_h = rng.gen_range(3..8);
let x = rng.gen_range(1..width - room_w - 1);
let y = rng.gen_range(1..height - room_h - 1);
for dy in 0..room_h {
for dx in 0..room_w {
dungeon[y + dy][x + dx] = '.';
}
}
}
// Add corridors
for _ in 0..10 {
let start_x = rng.gen_range(1..width - 1);
let start_y = rng.gen_range(1..height - 1);
let length = rng.gen_range(3..10);
let horizontal = rng.gen_bool(0.5);
for i in 0..length {
if horizontal && start_x + i < width - 1 {
dungeon[start_y][start_x + i] = '.';
} else if !horizontal && start_y + i < height - 1 {
dungeon[start_y + i][start_x] = '.';
}
}
}
// Place player and exit
loop {
let px = rng.gen_range(1..width - 1);
let py = rng.gen_range(1..height - 1);
if dungeon[py][px] == '.' {
dungeon[py][px] = '@';
break;
}
}
dungeon
}
fn main() {
let mut rng = SmallRng::seed_from_u64(12345);
let dungeon = generate_dungeon(&mut rng, 40, 20);
for row in &dungeon {
println!("{}", row.iter().collect::<String>());
}
// Same seed produces same dungeon
let mut rng2 = SmallRng::seed_from_u64(12345);
let dungeon2 = generate_dungeon(&mut rng2, 40, 20);
assert!(dungeon == dungeon2);
}Procedural generation benefits from fast generation and reproducible seeds.
use rand::{SeedableRng, Rng};
use rand::rngs::SmallRng;
// WRONG: Don't use SmallRng for security
fn generate_session_key_insecure() -> String {
let mut rng = SmallRng::from_entropy();
// VULNERABLE: Attacker can predict future keys
// after observing enough past keys
(0..32)
.map(|_| rng.gen_range(b'a'..=b'z') as char)
.collect()
}
// WRONG: Don't use SmallRng for passwords
fn generate_password_insecure() -> String {
let mut rng = SmallRng::from_entropy();
// VULNERABLE: Not enough entropy for security
(0..16)
.map(|_| rng.gen::<char>())
.collect()
}
// WRONG: Don't use SmallRng for cryptography
fn generate_iv_insecure() -> [u8; 16] {
let mut rng = SmallRng::from_entropy();
// VULNERABLE: IV must be unpredictable
let mut iv = [0u8; 16];
rng.fill_bytes(&mut iv);
iv
}
fn main() {
// These compile but are SECURITY VULNERABILITIES
// For security, use:
// - rand::rngs::StdRng (ChaCha20)
// - rand::rngs::OsRng (OS entropy)
// - rand::thread_rng() (cryptographically secure)
println!("Insecure session key: {}", generate_session_key_insecure());
println!("DO NOT use SmallRng for security!");
}SmallRng must never be used for security-sensitive applications.
use rand::{Rng, RngCore};
use rand::rngs::{StdRng, OsRng};
use rand::SeedableRng;
// CORRECT: Use StdRng for local crypto operations
fn generate_session_key_secure() -> String {
let mut rng = StdRng::from_entropy();
(0..32)
.map(|_| rng.gen_range(b'a'..=b'z') as char)
.collect()
}
// CORRECT: Use OsRng for maximum security
fn generate_password_secure() -> [u8; 32] {
let mut password = [0u8; 32];
OsRng.fill_bytes(&mut password);
password
}
// CORRECT: Use thread_rng for general secure use
fn generate_token_secure() -> u64 {
rand::thread_rng().gen()
}
fn main() {
println!("Secure session key: {}", generate_session_key_secure());
let pwd = generate_password_secure();
println!("Secure password bytes: {:?}", pwd);
println!("Secure token: {}", generate_token_secure());
}Use StdRng, OsRng, or thread_rng() for security-sensitive code.
use rand::{SeedableRng, RngCore};
use rand::rngs::SmallRng;
fn main() {
// Demonstrate why SmallRng is NOT cryptographically secure
// Given the seed, output is completely deterministic
let seed = 12345u64;
let mut rng1 = SmallRng::seed_from_u64(seed);
let mut rng2 = SmallRng::seed_from_u64(seed);
// Both produce identical sequences
for i in 0..5 {
let v1: u64 = rng1.gen();
let v2: u64 = rng2.gen();
println!("{}: {} == {}", i, v1, v2);
assert_eq!(v1, v2);
}
// An attacker who knows the algorithm can:
// 1. Observe enough output values
// 2. Recover the internal state
// 3. Predict all future values
// This is fine for games, simulations, tests
// But catastrophic for cryptography
println!("\nPredictability is OK for:");
println!(" - Game AI");
println!(" - Simulations");
println!(" - Procedural generation");
println!(" - Testing with fixed seeds");
}Determinism from known seeds is intentional for SmallRng—it's a feature, not a bug.
use rand::{Rng, thread_rng};
use rand::rngs::SmallRng;
use rand::SeedableRng;
use std::sync::Arc;
use std::thread;
fn main() {
// SmallRng is not thread-safe by itself
// Each thread needs its own instance
let mut handles = vec
![];
for seed in [1u64, 2, 3, 4] {
let handle = thread::spawn(move || {
// Each thread has its own SmallRng
let mut rng = SmallRng::seed_from_u64(seed);
let value: u32 = rng.gen_range(0..100);
format!("Thread {} got {}", seed, value)
});
handles.push(handle);
}
for handle in handles {
println!("{}", handle.join().unwrap());
}
// thread_rng() uses ThreadRng which is:
// - Thread-safe
// - Cryptographically secure (ChaCha20)
// - Slower than SmallRng but safer API
let values: Vec<u32> = (0..4)
.map(|_| thread_rng().gen_range(0..100))
.collect();
println!("thread_rng values: {:?}", values);
}SmallRng instances must not be shared across threads; use one per thread or use thread_rng().
use rand::{SeedableRng, Rng};
use rand::rngs::{SmallRng, StdRng};
use std::time::Instant;
fn main() {
const COUNT: usize = 10_000_000;
// u64 generation benchmark
let mut small_rng = SmallRng::from_entropy();
let start = Instant::now();
let mut sum: u64 = 0;
for _ in 0..COUNT {
sum = sum.wrapping_add(small_rng.gen::<u64>());
}
let small_u64 = start.elapsed();
let mut std_rng = StdRng::from_entropy();
let start = Instant::now();
let mut sum: u64 = 0;
for _ in 0..COUNT {
sum = sum.wrapping_add(std_rng.gen::<u64>());
}
let std_u64 = start.elapsed();
// fill_bytes benchmark
let mut small_rng = SmallRng::from_entropy();
let mut buf = [0u8; 1024];
let start = Instant::now();
for _ in 0..COUNT / 1000 {
small_rng.fill_bytes(&mut buf);
}
let small_bytes = start.elapsed();
let mut std_rng = StdRng::from_entropy();
let start = Instant::now();
for _ in 0..COUNT / 1000 {
std_rng.fill_bytes(&mut buf);
}
let std_bytes = start.elapsed();
println!("u64 generation ({} iterations):", COUNT);
println!(" SmallRng: {:?}", small_u64);
println!(" StdRng: {:?}", std_u64);
println!(" Speedup: {:.1}x", std_u64.as_secs_f64() / small_u64.as_secs_f64());
println!("\nfill_bytes ({}KB total):", COUNT);
println!(" SmallRng: {:?}", small_bytes);
println!(" StdRng: {:?}", std_bytes);
println!(" Speedup: {:.1}x", std_bytes.as_secs_f64() / small_bytes.as_secs_f64());
std::mem::forget(sum);
}Benchmarks typically show SmallRng is 3-10x faster than StdRng.
Performance comparison:
| Rng Type | Speed | Size | Crypto | Use Case |
|----------|-------|------|--------|----------|
| SmallRng | Very fast | 32 bytes | No | Games, simulation |
| StdRng | Medium | 112 bytes | Yes | Crypto, security |
| OsRng | Slow | N/A | Yes | Key generation |
| ThreadRng | Medium | Thread-local | Yes | General use |
When to use SmallRng:
| Appropriate | NOT Appropriate | |-------------|-----------------| | Game mechanics | Session tokens | | Monte Carlo simulation | Password generation | | Procedural generation | Encryption keys | | Shuffle algorithms | IV generation | | Testing (seeded) | CSRF tokens | | Benchmark baselines | Lottery numbers |
Security implications:
| Aspect | SmallRng | StdRng | |--------|----------|--------| | Predictability | Predictable from seed | Cryptographically unpredictable | | State recovery | Easy from output | Computationally infeasible | | Period | 2^256 - 1 | Effectively infinite | | Statistical quality | Excellent | Excellent |
Key insight: rand::rngs::SmallRng optimizes for speed through algorithmic simplicity—using Xoshiro256++ or similar PRNGs that require only a handful of XOR, rotate, and add operations per generated value, compared to the 20 rounds of quarter-round operations in ChaCha20 used by StdRng. This makes SmallRng ideal for simulations, games, and procedural generation where billions of random numbers may be needed and cryptographic unpredictability provides no value. The smaller state (32 bytes vs 112 bytes) also improves cache efficiency. However, this speed comes at the cost of cryptographic security: an attacker who observes sufficient output values can potentially recover the internal state and predict all future values, making SmallRng completely unsuitable for security-sensitive contexts. The key is recognizing that "good statistical properties" and "cryptographic security" are orthogonal requirements—SmallRng excels at the former while deliberately sacrificing the latter.