A crate rand é a biblioteca padrão da comunidade Rust para geração de números aleatórios. Ela fornece uma interface unificada para diferentes geradores (RNGs), distribuições estatísticas, seleção aleatória e shuffling. Desde jogos e simulações até criptografia e testes, rand é uma dependência onipresente no ecossistema Rust.
A crate é projetada em camadas: rand_core define as traits fundamentais, rand fornece a API de alto nível, e crates como rand_chacha e rand_pcg implementam algoritmos específicos. Essa modularidade permite que você escolha exatamente o nível de qualidade e performance que precisa.
Instalação
Adicione ao seu Cargo.toml:
[dependencies]
rand = "0.8"
Para distribuições estatísticas adicionais:
[dependencies]
rand = "0.8"
rand_distr = "0.4"
Para RNG com seed reproduzível:
[dependencies]
rand = "0.8"
rand_chacha = "0.3"
Uso Básico
Gerando Números Aleatórios
use rand::Rng;
fn main() {
let mut rng = rand::thread_rng();
// Inteiros
let n: i32 = rng.gen();
println!("i32 aleatório: {}", n);
// Inteiro em um intervalo [0, 100)
let n: i32 = rng.gen_range(0..100);
println!("0 a 99: {}", n);
// Intervalo inclusivo [1, 6]
let dado: u32 = rng.gen_range(1..=6);
println!("Dado: {}", dado);
// Float entre 0.0 e 1.0
let f: f64 = rng.gen();
println!("Float [0, 1): {:.4}", f);
// Float em intervalo
let temperatura: f64 = rng.gen_range(20.0..35.0);
println!("Temperatura: {:.1}°C", temperatura);
// Booleano (50/50)
let cara: bool = rng.gen();
println!("Moeda: {}", if cara { "Cara" } else { "Coroa" });
// Booleano com probabilidade
let chuva: bool = rng.gen_bool(0.3); // 30% de chance
println!("Vai chover? {}", chuva);
}
Gerando Tipos Compostos
use rand::Rng;
fn main() {
let mut rng = rand::thread_rng();
// Tuplas
let ponto: (f64, f64) = rng.gen();
println!("Ponto: ({:.2}, {:.2})", ponto.0, ponto.1);
// Arrays
let bytes: [u8; 16] = rng.gen();
println!("Bytes: {:?}", bytes);
// Preenchendo um slice
let mut buffer = [0u8; 32];
rng.fill(&mut buffer);
println!("Buffer: {:02x?}", &buffer[..8]);
// Gerando Vec de números
let numeros: Vec<i32> = (0..10).map(|_| rng.gen_range(1..=100)).collect();
println!("Números: {:?}", numeros);
}
Seleção Aleatória de Coleções
use rand::seq::SliceRandom;
use rand::thread_rng;
fn main() {
let mut rng = thread_rng();
let frutas = vec!["maçã", "banana", "laranja", "uva", "manga"];
// Escolher um elemento
let fruta = frutas.choose(&mut rng).unwrap();
println!("Fruta escolhida: {}", fruta);
// Escolher múltiplos elementos (sem repetição)
let selecionadas: Vec<&&str> = frutas.choose_multiple(&mut rng, 3).collect();
println!("Selecionadas: {:?}", selecionadas);
// Seleção ponderada
let opcoes = vec![
("Comum", 70),
("Raro", 20),
("Épico", 8),
("Lendário", 2),
];
let escolha = opcoes
.choose_weighted(&mut rng, |item| item.1)
.unwrap();
println!("Item dropado: {} (peso: {})", escolha.0, escolha.1);
}
Embaralhamento (Shuffle)
use rand::seq::SliceRandom;
use rand::thread_rng;
fn main() {
let mut rng = thread_rng();
// Embaralhar um vetor
let mut cartas: Vec<String> = Vec::new();
let naipes = ["Copas", "Ouros", "Espadas", "Paus"];
let valores = [
"A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K",
];
for naipe in &naipes {
for valor in &valores {
cartas.push(format!("{} de {}", valor, naipe));
}
}
cartas.shuffle(&mut rng);
println!("Baralho embaralhado (5 primeiras):");
for carta in &cartas[..5] {
println!(" {}", carta);
}
// Embaralhamento parcial (Fisher-Yates parcial)
let mut numeros: Vec<i32> = (1..=52).collect();
let (embaralhado, _) = numeros.partial_shuffle(&mut rng, 5);
println!("\n5 números aleatórios de 1-52: {:?}", embaralhado);
}
Recursos Avançados
Distribuições Estatísticas
use rand::Rng;
use rand_distr::{Bernoulli, Normal, Poisson, Uniform, Distribution};
fn main() {
let mut rng = rand::thread_rng();
// Uniforme (mais eficiente que gen_range para amostragem repetida)
let uniforme = Uniform::new(1, 7); // [1, 7) = dado de 6 faces
let dados: Vec<i32> = (0..10).map(|_| uniforme.sample(&mut rng)).collect();
println!("10 dados: {:?}", dados);
// Normal (Gaussiana)
let normal = Normal::new(170.0, 10.0).unwrap(); // média=170, desvio=10
let alturas: Vec<f64> = (0..5).map(|_| normal.sample(&mut rng)).collect();
println!("Alturas (cm): {:?}", alturas.iter().map(|h| format!("{:.1}", h)).collect::<Vec<_>>());
// Bernoulli (sucesso/falha)
let moeda = Bernoulli::new(0.5).unwrap();
let lancamentos: Vec<bool> = (0..10).map(|_| moeda.sample(&mut rng)).collect();
let caras = lancamentos.iter().filter(|&&x| x).count();
println!("10 lançamentos: {} caras, {} coroas", caras, 10 - caras);
// Poisson (eventos por intervalo)
let poisson = Poisson::new(4.0).unwrap(); // média de 4 eventos
let eventos: Vec<f64> = (0..10).map(|_| poisson.sample(&mut rng)).collect();
println!("Eventos por hora: {:?}", eventos.iter().map(|e| *e as u32).collect::<Vec<_>>());
}
RNG com Seed (Reproduzível)
use rand::SeedableRng;
use rand::Rng;
use rand_chacha::ChaCha8Rng;
fn main() {
// Mesmo seed = mesma sequência (reproduzível)
let mut rng1 = ChaCha8Rng::seed_from_u64(42);
let mut rng2 = ChaCha8Rng::seed_from_u64(42);
let seq1: Vec<i32> = (0..5).map(|_| rng1.gen_range(1..=100)).collect();
let seq2: Vec<i32> = (0..5).map(|_| rng2.gen_range(1..=100)).collect();
println!("Sequência 1: {:?}", seq1);
println!("Sequência 2: {:?}", seq2);
assert_eq!(seq1, seq2); // Sempre iguais!
// Seed a partir de bytes
let seed: [u8; 32] = [0; 32];
let mut rng3 = ChaCha8Rng::from_seed(seed);
println!("Com seed de bytes: {}", rng3.gen::<u32>());
// Seed a partir de outro RNG (útil para testes)
let mut rng4 = ChaCha8Rng::from_rng(rand::thread_rng()).unwrap();
println!("Com seed de thread_rng: {}", rng4.gen::<u32>());
}
Aleatoriedade Criptográfica
use rand::rngs::OsRng;
use rand::RngCore;
use rand::Rng;
fn main() {
// OsRng usa a fonte de entropia do sistema operacional
let mut rng = OsRng;
// Gerar bytes criptograficamente seguros
let mut chave = [0u8; 32];
rng.fill_bytes(&mut chave);
println!(
"Chave 256-bit: {}",
chave.iter().map(|b| format!("{:02x}", b)).collect::<String>()
);
// Gerar token
let mut token = [0u8; 16];
rng.fill_bytes(&mut token);
println!(
"Token: {}",
token.iter().map(|b| format!("{:02x}", b)).collect::<String>()
);
// Gerar número aleatório criptográfico
let n: u64 = rng.gen();
println!("u64 criptográfico: {}", n);
}
Gerando Strings Aleatórias
use rand::distributions::Alphanumeric;
use rand::Rng;
fn gerar_string_aleatoria(tamanho: usize) -> String {
rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(tamanho)
.map(char::from)
.collect()
}
fn gerar_com_charset(tamanho: usize, charset: &[u8]) -> String {
let mut rng = rand::thread_rng();
(0..tamanho)
.map(|_| {
let idx = rng.gen_range(0..charset.len());
charset[idx] as char
})
.collect()
}
fn main() {
// Alfanumérico
println!("Alfanumérico: {}", gerar_string_aleatoria(20));
// Apenas letras minúsculas
let minusculas = b"abcdefghijklmnopqrstuvwxyz";
println!("Minúsculas: {}", gerar_com_charset(10, minusculas));
// Hexadecimal
let hex = b"0123456789abcdef";
println!("Hex: {}", gerar_com_charset(16, hex));
// Código numérico (como OTP)
let digitos = b"0123456789";
println!("OTP: {}", gerar_com_charset(6, digitos));
}
Iteradores Aleatórios
use rand::distributions::{Distribution, Standard, Uniform};
use rand::Rng;
#[derive(Debug)]
enum Cor {
Vermelho,
Verde,
Azul,
Amarelo,
}
impl Distribution<Cor> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Cor {
match rng.gen_range(0..4) {
0 => Cor::Vermelho,
1 => Cor::Verde,
2 => Cor::Azul,
_ => Cor::Amarelo,
}
}
}
fn main() {
let mut rng = rand::thread_rng();
// Gerar cores aleatórias
let cores: Vec<Cor> = (0..5).map(|_| rng.gen()).collect();
println!("Cores: {:?}", cores);
// Iterator infinito com sample_iter
let uniforme = Uniform::new(0.0f64, 1.0);
let amostras: Vec<f64> = uniforme
.sample_iter(&mut rng)
.take(5)
.collect();
println!("Amostras: {:?}", amostras);
}
Boas Práticas
1. Reutilize o RNG
use rand::Rng;
// RUIM: cria novo RNG a cada chamada
fn aleatorio_ruim() -> i32 {
rand::thread_rng().gen_range(1..=100)
}
// BOM: recebe RNG como parâmetro
fn aleatorio_bom(rng: &mut impl Rng) -> i32 {
rng.gen_range(1..=100)
}
fn main() {
let mut rng = rand::thread_rng();
// Gerar múltiplos valores com o mesmo RNG
let valores: Vec<i32> = (0..10).map(|_| aleatorio_bom(&mut rng)).collect();
println!("{:?}", valores);
}
2. Use Seed para Testes Reproduzíveis
use rand::SeedableRng;
use rand::Rng;
use rand_chacha::ChaCha8Rng;
fn embaralhar_e_pegar<R: Rng>(rng: &mut R, n: usize) -> Vec<u32> {
use rand::seq::SliceRandom;
let mut numeros: Vec<u32> = (1..=52).collect();
numeros.shuffle(rng);
numeros[..n].to_vec()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn teste_reproduzivel() {
let mut rng = ChaCha8Rng::seed_from_u64(12345);
let resultado = embaralhar_e_pegar(&mut rng, 5);
// Sempre produz o mesmo resultado com o mesmo seed
let mut rng2 = ChaCha8Rng::seed_from_u64(12345);
let resultado2 = embaralhar_e_pegar(&mut rng2, 5);
assert_eq!(resultado, resultado2);
}
}
3. Escolha o RNG Correto
// Para uso geral (rápido, boa qualidade):
let mut rng = rand::thread_rng(); // SmallRng internamente
// Para criptografia (mais lento, mas seguro):
use rand::rngs::OsRng;
let mut rng = OsRng;
// Para reprodutibilidade em testes:
use rand::SeedableRng;
use rand_chacha::ChaCha8Rng;
let mut rng = ChaCha8Rng::seed_from_u64(42);
// Para performance máxima (não criptográfico):
use rand::rngs::SmallRng;
let mut rng = SmallRng::from_entropy();
4. Use Uniform para Amostragem Repetida
use rand::distributions::{Distribution, Uniform};
use rand::Rng;
fn main() {
let mut rng = rand::thread_rng();
// RUIM: recalcula os limites a cada chamada
for _ in 0..1000 {
let _n: i32 = rng.gen_range(1..=100);
}
// BOM: cria a distribuição uma vez
let dist = Uniform::new_inclusive(1, 100);
for _ in 0..1000 {
let _n: i32 = dist.sample(&mut rng);
}
}
5. Não Use rand para Segurança sem OsRng
use rand::Rng;
use rand::rngs::OsRng;
// RUIM: thread_rng NÃO é adequado para tokens de segurança
fn token_inseguro() -> String {
let mut rng = rand::thread_rng();
(0..32).map(|_| format!("{:02x}", rng.gen::<u8>())).collect()
}
// BOM: use OsRng para tokens criptográficos
fn token_seguro() -> String {
let mut rng = OsRng;
let mut bytes = [0u8; 32];
rand::RngCore::fill_bytes(&mut rng, &mut bytes);
bytes.iter().map(|b| format!("{:02x}", b)).collect()
}
Exemplos Práticos
Exemplo 1: Gerador de Senhas
use rand::seq::SliceRandom;
use rand::Rng;
#[derive(Debug)]
struct ConfigSenha {
tamanho: usize,
maiusculas: bool,
minusculas: bool,
numeros: bool,
especiais: bool,
excluir: String,
}
impl Default for ConfigSenha {
fn default() -> Self {
ConfigSenha {
tamanho: 16,
maiusculas: true,
minusculas: true,
numeros: true,
especiais: true,
excluir: String::new(),
}
}
}
fn gerar_senha(config: &ConfigSenha) -> Result<String, String> {
let mut charset = Vec::new();
let mut obrigatorios: Vec<char> = Vec::new();
let mut rng = rand::thread_rng();
if config.maiusculas {
let chars: Vec<char> = ('A'..='Z')
.filter(|c| !config.excluir.contains(*c))
.collect();
if let Some(&c) = chars.choose(&mut rng) {
obrigatorios.push(c);
}
charset.extend(chars);
}
if config.minusculas {
let chars: Vec<char> = ('a'..='z')
.filter(|c| !config.excluir.contains(*c))
.collect();
if let Some(&c) = chars.choose(&mut rng) {
obrigatorios.push(c);
}
charset.extend(chars);
}
if config.numeros {
let chars: Vec<char> = ('0'..='9')
.filter(|c| !config.excluir.contains(*c))
.collect();
if let Some(&c) = chars.choose(&mut rng) {
obrigatorios.push(c);
}
charset.extend(chars);
}
if config.especiais {
let chars: Vec<char> = "!@#$%&*()-_=+[]{}|;:,.<>?"
.chars()
.filter(|c| !config.excluir.contains(*c))
.collect();
if let Some(&c) = chars.choose(&mut rng) {
obrigatorios.push(c);
}
charset.extend(chars);
}
if charset.is_empty() {
return Err("Nenhum conjunto de caracteres selecionado".to_string());
}
if config.tamanho < obrigatorios.len() {
return Err("Tamanho muito pequeno para os requisitos".to_string());
}
// Gerar os caracteres restantes
let restantes = config.tamanho - obrigatorios.len();
let mut senha: Vec<char> = obrigatorios;
for _ in 0..restantes {
senha.push(*charset.choose(&mut rng).unwrap());
}
// Embaralhar para não ter padrão previsível
senha.shuffle(&mut rng);
Ok(senha.into_iter().collect())
}
fn avaliar_forca(senha: &str) -> (&str, u32) {
let mut pontos: u32 = 0;
if senha.len() >= 8 { pontos += 1; }
if senha.len() >= 12 { pontos += 1; }
if senha.len() >= 16 { pontos += 1; }
if senha.chars().any(|c| c.is_uppercase()) { pontos += 1; }
if senha.chars().any(|c| c.is_lowercase()) { pontos += 1; }
if senha.chars().any(|c| c.is_numeric()) { pontos += 1; }
if senha.chars().any(|c| !c.is_alphanumeric()) { pontos += 1; }
let nivel = match pontos {
0..=2 => "Fraca",
3..=4 => "Média",
5..=6 => "Forte",
_ => "Muito Forte",
};
(nivel, pontos)
}
fn main() {
println!("=== Gerador de Senhas ===\n");
// Senha padrão
let config = ConfigSenha::default();
let senha = gerar_senha(&config).unwrap();
let (forca, pontos) = avaliar_forca(&senha);
println!("Senha padrão: {} (Força: {} - {}/7)", senha, forca, pontos);
// Senha somente alfanumérica
let config = ConfigSenha {
tamanho: 20,
especiais: false,
..Default::default()
};
let senha = gerar_senha(&config).unwrap();
println!("Alfanumérica: {}", senha);
// Senha curta para PIN
let config = ConfigSenha {
tamanho: 6,
maiusculas: false,
minusculas: false,
numeros: true,
especiais: false,
excluir: String::new(),
};
let senha = gerar_senha(&config).unwrap();
println!("PIN numérico: {}", senha);
// Senha sem caracteres ambíguos
let config = ConfigSenha {
tamanho: 16,
excluir: "0Oo1lI|".to_string(),
..Default::default()
};
let senha = gerar_senha(&config).unwrap();
println!("Sem ambíguos: {}", senha);
// Gerar múltiplas senhas
println!("\n--- 5 senhas de 12 caracteres ---");
let config = ConfigSenha {
tamanho: 12,
..Default::default()
};
for i in 1..=5 {
let senha = gerar_senha(&config).unwrap();
let (forca, _) = avaliar_forca(&senha);
println!(" {}: {} ({})", i, senha, forca);
}
}
Exemplo 2: Simulação de Dados e Monte Carlo
use rand::Rng;
use rand::seq::SliceRandom;
use std::collections::HashMap;
fn simular_dados(num_dados: u32, num_lancamentos: u32) -> HashMap<u32, u32> {
let mut rng = rand::thread_rng();
let mut contagem: HashMap<u32, u32> = HashMap::new();
for _ in 0..num_lancamentos {
let soma: u32 = (0..num_dados).map(|_| rng.gen_range(1..=6)).sum();
*contagem.entry(soma).or_insert(0) += 1;
}
contagem
}
fn estimar_pi(num_pontos: u64) -> f64 {
let mut rng = rand::thread_rng();
let mut dentro_circulo: u64 = 0;
for _ in 0..num_pontos {
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 {
dentro_circulo += 1;
}
}
4.0 * (dentro_circulo as f64) / (num_pontos as f64)
}
fn problema_do_aniversario(tamanho_grupo: usize, num_simulacoes: u32) -> f64 {
let mut rng = rand::thread_rng();
let mut colisoes: u32 = 0;
for _ in 0..num_simulacoes {
let mut aniversarios = std::collections::HashSet::new();
let mut colidiu = false;
for _ in 0..tamanho_grupo {
let dia: u16 = rng.gen_range(1..=365);
if !aniversarios.insert(dia) {
colidiu = true;
break;
}
}
if colidiu {
colisoes += 1;
}
}
colisoes as f64 / num_simulacoes as f64
}
fn problema_monty_hall(num_simulacoes: u32) -> (f64, f64) {
let mut rng = rand::thread_rng();
let mut vitorias_trocando = 0u32;
let mut vitorias_mantendo = 0u32;
for _ in 0..num_simulacoes {
let premio: usize = rng.gen_range(0..3);
let escolha: usize = rng.gen_range(0..3);
// Se mantém a escolha: ganha se escolheu a porta certa
if escolha == premio {
vitorias_mantendo += 1;
} else {
// Se troca: ganha se NÃO escolheu a porta certa
vitorias_trocando += 1;
}
}
(
vitorias_mantendo as f64 / num_simulacoes as f64,
vitorias_trocando as f64 / num_simulacoes as f64,
)
}
fn main() {
println!("=== Simulações com Rand ===\n");
// Simulação de dados
println!("--- Distribuição de 2 dados (100.000 lançamentos) ---");
let resultados = simular_dados(2, 100_000);
let mut somas: Vec<u32> = resultados.keys().cloned().collect();
somas.sort();
for soma in somas {
let contagem = resultados[&soma];
let percentual = contagem as f64 / 100_000.0 * 100.0;
let barra = "#".repeat((percentual * 2.0) as usize);
println!(" {:2}: {:5} ({:5.2}%) {}", soma, contagem, percentual, barra);
}
// Estimativa de Pi (Monte Carlo)
println!("\n--- Estimativa de Pi (Monte Carlo) ---");
for &n in &[1_000, 10_000, 100_000, 1_000_000] {
let pi = estimar_pi(n);
let erro = (pi - std::f64::consts::PI).abs();
println!(" {:>10} pontos: Pi ~ {:.6} (erro: {:.6})", n, pi, erro);
}
// Problema do aniversário
println!("\n--- Problema do Aniversário (10.000 simulações) ---");
for &grupo in &[10, 20, 23, 30, 40, 50] {
let prob = problema_do_aniversario(grupo, 10_000);
println!(" {} pessoas: {:.1}% de colisão", grupo, prob * 100.0);
}
// Problema de Monty Hall
println!("\n--- Problema de Monty Hall (100.000 simulações) ---");
let (mantendo, trocando) = problema_monty_hall(100_000);
println!(" Mantendo: {:.1}% de vitória", mantendo * 100.0);
println!(" Trocando: {:.1}% de vitória", trocando * 100.0);
}
Comparação com Alternativas
| Crate | Caso de uso | Performance | Criptográfico |
|---|---|---|---|
rand | Uso geral | Excelente | Parcial (OsRng) |
fastrand | Ultra rápido | Superior | Não |
getrandom | Entropia do OS | N/A | Sim |
ring | Criptografia | Boa | Sim |
nanorand | Mínimo, rápido | Excelente | Não |
Use rand para a maioria dos casos. Use fastrand se precisa de performance máxima sem requisitos criptográficos. Para criptografia real, considere ring ou rustcrypto.
Conclusão
A crate rand é a base da geração de números aleatórios em Rust. Com sua API intuitiva e tipada, suporte a diversas distribuições, RNGs com seed para reprodutibilidade e aleatoriedade criptográfica via OsRng, ela cobre desde jogos simples até simulações científicas.
Lembre-se de reutilizar o RNG, usar seed para testes reproduzíveis, escolher o gerador adequado para seu caso (performance vs. segurança) e usar distribuições Uniform para amostragem repetida.
Próximos passos: