Rand: Geração de Números Aleatórios em Rust

Guia completo da crate rand para Rust. Aprenda thread_rng, distribuições, RNG com seed, seleção aleatória, shuffling, aleatoriedade criptográfica com OsRng e exemplos práticos.

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

CrateCaso de usoPerformanceCriptográfico
randUso geralExcelenteParcial (OsRng)
fastrandUltra rápidoSuperiorNão
getrandomEntropia do OSN/ASim
ringCriptografiaBoaSim
nanorandMínimo, rápidoExcelenteNã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:

  • Explore proptest para testes baseados em propriedades usando geração aleatória
  • Veja criterion para benchmarking de código que usa rand
  • Confira chrono para combinar aleatoriedade com cálculos temporais