Números aleatórios em Rust com rand
Jogos rolam dados. Quizzes embaralham as perguntas. Testes precisam de dados de exemplo. Tudo isso precisa de números aleatórios, e a biblioteca padrão do Rust não tem um gerador de números aleatórios pronto. Em vez disso, quase todo mundo usa um crate (pacote): o rand (em inglês).
Esta página mostra a API moderna do rand 0.10. Isso importa, porque o rand mudou alguns nomes nas versões 0.9 e 0.10. Se um tutorial mais antigo mostra thread_rng() ou gen_range(), ele foi escrito para uma versão anterior. Vamos ver os nomes novos e os erros que os nomes antigos causam.
Adicionando o rand ao seu projeto
Seção intitulada “Adicionando o rand ao seu projeto”Na pasta do seu projeto, rode:
cargo add randIsso acrescenta uma linha como esta ao seu Cargo.toml:
[dependencies]rand = "0.10"Se crates e o Cargo.toml são novidade para você, leia essa página antes. Os botões “Experimente você mesmo” desta página abrem o Rust Playground, que já inclui o rand.
Valores aleatórios rápidos
Seção intitulada “Valores aleatórios rápidos”Para um valor aleatório avulso, use as funções livres rand::random_range e rand::random:
fn main() { let die: u32 = rand::random_range(1..=6); let coin: bool = rand::random(); let chance: f64 = rand::random();
println!("o dado está entre 1 e 6: {}", (1..=6).contains(&die)); println!("chance está em 0.0..1.0: {}", (0.0..1.0).contains(&chance)); println!("a moeda deu cara: {coin}");}Não tem bloco de saída aqui de propósito: a última linha é true ou false ao acaso. Clique em “Experimente você mesmo” algumas vezes e veja mudar.
random_range(1..=6)dá um número de 1 a 6, incluindo o 6. Com1..6você só teria de 1 a 5. Os intervalos funcionam igual aos de um laço for.random()escolhe um valor do tipo que você pedir. Para umbool, é um cara ou coroa justo. Para umf64, é um número de0.0até1.0(sem incluir o1.0).- A anotação de tipo (
: u32,: bool) diz aorando que gerar. Sem ela, o Rust pode não saber qual tipo você quer.
Um gerador e a trait RngExt
Seção intitulada “Um gerador e a trait RngExt”Quando você precisa de muitos valores aleatórios, pegue um gerador uma vez com rand::rng() e chame métodos nele. Isso é um pouco mais rápido, e é o estilo que você vai ver na maior parte do código:
use rand::RngExt;
fn main() { let mut rng = rand::rng();
let rolls: Vec<u32> = (0..1000).map(|_| rng.random_range(1..=6)).collect(); println!("rolou {} dados", rolls.len()); println!("todos entre 1 e 6: {}", rolls.iter().all(|r| (1..=6).contains(r)));
let x: f64 = rng.random(); println!("x está em 0.0..1.0: {}", (0.0..1.0).contains(&x));}Saída:
rolou 1000 dadostodos entre 1 e 6: truex está em 0.0..1.0: trueImprimimos verificações em vez dos próprios números, para que a saída seja sempre a mesma, mesmo que os valores não sejam.
Mais dois métodos que você vai usar bastante:
let lucky = rng.random_bool(0.1); // true em cerca de 10% das vezeslet rare = rng.random_ratio(1, 100); // true em cerca de 1 vez a cada 100Por que a linha use rand::RngExt;? Métodos como random_range e random_bool vêm de uma trait, e você só pode chamar os métodos de uma trait quando ela está no escopo. No rand 0.10 existem duas traits:
Rngé a trait básica de “eu sei produzir bits aleatórios”. Todo gerador a implementa.RngExtacrescenta os métodos práticos:random,random_range,random_boole outros. Ela funciona automaticamente em todoRng.
Então: importe RngExt para chamar os métodos, e use Rng quando escrever uma função que aceita “qualquer gerador”. Você vai ver as duas no rolador de dados mais abaixo.
Resultados reproduzíveis com uma seed
Seção intitulada “Resultados reproduzíveis com uma seed”rand::rng() dá números diferentes a cada execução. É isso que você quer em um jogo de verdade. Mas, para testes, depuração ou a fase de “desafio do dia” de um jogo, você quer os mesmos números “aleatórios” toda vez.
Uma seed (semente) faz isso. Um gerador com seed sempre produz a mesma sequência para a mesma seed:
use rand::rngs::StdRng;use rand::{RngExt, SeedableRng};
fn main() { let mut a = StdRng::seed_from_u64(2024); let mut b = StdRng::seed_from_u64(2024);
let first: Vec<u8> = (0..4).map(|_| a.random_range(0..100)).collect(); let second: Vec<u8> = (0..4).map(|_| b.random_range(0..100)).collect();
println!("{:?}", first); println!("{:?}", second); println!("iguais? {}", first == second);}Saída:
[53, 82, 74, 51][53, 82, 74, 51]iguais? trueseed_from_u64 vem da trait SeedableRng, então ela também precisa ser importada. Troque 2024 por outro número e você ganha uma sequência diferente, mas ainda a mesma em todas as execuções.
Um aviso sincero: o algoritmo do StdRng pode mudar em uma versão futura do rand. Os números acima vêm do rand 0.10. Se os seus testes precisam dar exatamente os mesmos números para sempre, o livro do rand recomenda um gerador com nome fixo, como o ChaCha8Rng (veja “Para saber mais” abaixo).
Embaralhar e escolher de uma slice
Seção intitulada “Embaralhar e escolher de uma slice”Para trabalhar com listas, importe mais duas traits de rand::seq:
SliceRandomdá oshuffle, que embaralha uma slice (fatia) no próprio lugar.IndexedRandomdá ochoose(um item aleatório) e osample(vários itens diferentes).
use rand::rngs::StdRng;use rand::seq::{IndexedRandom, SliceRandom};use rand::SeedableRng;
fn main() { let mut rng = StdRng::seed_from_u64(7);
let mut cards = vec!["A", "K", "Q", "J", "10"]; cards.shuffle(&mut rng); println!("embaralhadas: {:?}", cards);
let colors = ["vermelho", "verde", "azul"]; println!("escolhida: {:?}", colors.choose(&mut rng));
let team: Vec<&str> = cards.sample(&mut rng, 2).copied().collect(); println!("time: {:?}", team);
let empty: [i32; 0] = []; println!("de uma vazia: {:?}", empty.choose(&mut rng));}Saída:
embaralhadas: ["K", "J", "10", "Q", "A"]escolhida: Some("vermelho")time: ["K", "J"]de uma vazia: Nonechoose devolve um Option, porque uma slice vazia não tem nada para escolher. Esses métodos funcionam do mesmo jeito em arrays, vetores e slices. Troque o rng com seed por rand::rng() e você ganha um embaralhamento novo a cada execução.
Exemplo: um rolador de dados
Seção intitulada “Exemplo: um rolador de dados”Vamos escrever uma função que rola dados. O segredo está no tipo do parâmetro: &mut impl Rng significa “qualquer gerador aleatório”. No jogo de verdade, você passa rand::rng(). Em um teste, você passa um StdRng com seed e sabe exatamente o que vai sair:
use rand::rngs::StdRng;use rand::{Rng, RngExt, SeedableRng};
/// Rola `count` dados com `sides` lados cada.fn roll(rng: &mut impl Rng, count: u32, sides: u32) -> Vec<u32> { (0..count).map(|_| rng.random_range(1..=sides)).collect()}
fn main() { let mut rng = StdRng::seed_from_u64(1);
for (count, sides) in [(2, 6), (1, 20), (3, 4)] { let rolls = roll(&mut rng, count, sides); let total: u32 = rolls.iter().sum(); println!("{count}d{sides}: {:?} = {total}", rolls); }}Saída:
2d6: [5, 6] = 111d20: [16] = 163d4: [3, 1, 2] = 62d6 é a abreviação de jogos de tabuleiro e RPG para “dois dados de seis lados”. Passar o gerador como parâmetro, em vez de chamar rand::rng() dentro de roll, é um bom hábito: deixa a função fácil de testar.
Exemplo: um gerador de senhas aleatórias
Seção intitulada “Exemplo: um gerador de senhas aleatórias”Aqui escolhemos 16 caracteres de um conjunto permitido. O conjunto deixa de fora caracteres parecidos, como 0/O e 1/l, para que a senha seja mais fácil de digitar:
use rand::seq::IndexedRandom;use rand::Rng;
const CHARSET: &[u8] = b"ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789!@#$%&*";
fn password(rng: &mut impl Rng, length: usize) -> String { (0..length) .map(|_| *CHARSET.choose(rng).unwrap() as char) .collect()}
fn main() { let mut rng = rand::rng(); let pw = password(&mut rng, 16);
println!("tamanho: {}", pw.len()); println!("só caracteres permitidos: {}", pw.bytes().all(|b| CHARSET.contains(&b))); // println!("{pw}"); // tire o comentário para ver: muda a cada execução}Saída:
tamanho: 16só caracteres permitidos: trueb"..." é uma byte string: um &[u8] de caracteres ASCII. choose escolhe um byte, e as char o transforma em um caractere. Repare que usamos rand::rng() aqui, e nunca um gerador com seed. Uma senha gerada a partir de uma seed fixa é a mesma senha toda vez, ou seja, não é senha nenhuma.
De volta ao jogo de adivinhação
Seção intitulada “De volta ao jogo de adivinhação”O projeto do Jogo de adivinhação escolhe o número secreto com um truque usando o relógio, para continuar usando só a biblioteca padrão. Com o rand, a função secret_number inteira vira uma linha:
let secret: u32 = rand::random_range(1..=100);Todo o resto do jogo continua igual. Aqui vai uma versão que você pode rodar no Playground. O número secreto vem de um gerador com seed, e o computador tenta adivinhar sempre escolhendo o meio do intervalo que sobrou:
use rand::rngs::StdRng;use rand::{RngExt, SeedableRng};use std::cmp::Ordering;
fn main() { // No jogo de verdade: let secret: u32 = rand::random_range(1..=100); let mut rng = StdRng::seed_from_u64(99); let secret: u32 = rng.random_range(1..=100);
let (mut low, mut high) = (1, 100); let mut attempts = 0; loop { let guess = (low + high) / 2; attempts += 1; match guess.cmp(&secret) { Ordering::Less => { println!("{guess} é pequeno demais"); low = guess + 1; } Ordering::Greater => { println!("{guess} é grande demais"); high = guess - 1; } Ordering::Equal => { println!("{guess} está certo! Encontrado em {attempts} tentativas."); break; } } }}Saída:
50 é pequeno demais75 é pequeno demais88 é pequeno demais94 é pequeno demais97 é pequeno demais99 está certo! Encontrado em 6 tentativas.Curiosidade: essa estratégia de “dividir o intervalo ao meio” nunca precisa de mais de 7 palpites para números de 1 a 100.
O rand é seguro para senhas e chaves?
Seção intitulada “O rand é seguro para senhas e chaves?”Resposta curta: rand::rng() foi feito para ser difícil de prever, mas, para segredos sérios, use ferramentas feitas para isso.
rand::rng()usa um algoritmo criptograficamente seguro (atualmente o ChaCha12). Ele recebe a seed da fonte de aleatoriedade do seu sistema operacional, por meio do crategetrandom(em inglês), e troca a própria seed regularmente. A documentação do rand o chama de “gerador rápido e razoavelmente seguro”, e diz que não pode prometer que ele atende a todas as necessidades de segurança.- Para chaves de criptografia, tokens de API e outros segredos importantes, pegue os bytes direto do sistema operacional com o
getrandom(ourand::rngs::SysRng) ou, melhor ainda, deixe uma biblioteca de criptografia dedicada gerar a chave para você. - Nunca use um gerador com seed (
seed_from_u64) ou o rápidoSmallRngpara algo secreto. Qualquer pessoa que saiba ou adivinhe a seed consegue produzir os mesmos valores “aleatórios”.
Para jogos, simulações, embaralhamentos e dados de teste, rand::rng() é exatamente o certo.
Erros comuns
Seção intitulada “Erros comuns”Importar Rng em vez de RngExt. No rand 0.9 os métodos ficavam em Rng, então código mais antigo faz use rand::Rng;. Com o rand 0.10, isso dá:
use rand::Rng;
fn main() { let mut rng = rand::rng(); let n = rng.random_range(1..=6); println!("{n}");}error[E0599]: no method named `random_range` found for struct `ThreadRng` in the current scope --> src/main.rs:5:17 |5 | let n = rng.random_range(1..=6); | ^^^^^^^^^^^^ | = help: items from traits can only be used if the trait is in scopehelp: trait `RngExt` which provides `random_range` is implemented but not in scope; perhaps you want to import it |1 + use rand::RngExt; |O compilador diz que não encontrou o método random_range, explica que métodos de traits só podem ser usados quando a trait está no escopo, e já mostra a solução: use rand::RngExt;.
Copiar código escrito para o rand 0.8. Exemplos bem antigos usam thread_rng() e gen_range():
fn main() { let n = rand::thread_rng().gen_range(1..=6); println!("{n}");}error[E0425]: cannot find function `thread_rng` in crate `rand` --> src/main.rs:2:19 |2 | let n = rand::thread_rng().gen_range(1..=6); | ^^^^^^^^^^ not found in `rand`O erro diz que a função thread_rng não existe no crate rand. Os nomes novos são rand::rng() e random_range(). Da mesma forma, gen() virou random() e gen_bool() virou random_bool().
Intervalos com um a menos. random_range(1..6) nunca devolve 6. Para um dado, escreva 1..=6.
- Adicione o crate com
cargo add rand. A biblioteca padrão do Rust não tem um gerador de números aleatórios próprio. - Para valores rápidos, use
rand::random_range(1..=6)erand::random::<T>(). - Para muitos valores, use
let mut rng = rand::rng();comuse rand::RngExt;para métodos comorandom_rangeerandom_bool. SliceRandom::shuffleembaralha uma slice;IndexedRandom::chooseescolhe um item e devolve umOption.StdRng::seed_from_u64(n)dá a mesma sequência em todas as execuções: ótimo para testes e fases de jogo repetíveis, nunca para segredos.- Para chaves e tokens, use a fonte de aleatoriedade do sistema operacional (
getrandom) ou um crate de criptografia dedicado.
Teste o que aprendeu
Seção intitulada “Teste o que aprendeu”Teste o que aprendeu
1. Qual chamada pode devolver 6?
2. Com o rand 0.10, rng.random_range(..) falha com "no method named random_range". O que está faltando?
3. Por que você usaria StdRng::seed_from_u64(42)?
4. O que choose devolve quando você o chama em uma slice vazia?
Pontuação: 0 / 4
Para saber mais
Seção intitulada “Para saber mais”- The Rust Rand Book (em inglês): o guia oficial do
rand, do início rápido até a escolha de um gerador. - Documentação da API do
rand— docs.rs (em inglês): todas as funções, traits e geradores, com exemplos. - Updating to 0.10 — The Rust Rand Book (em inglês): o que mudou na 0.10, incluindo a troca de nomes entre
RngeRngExt. Útil quando você encontrar código mais antigo. - Our RNGs — The Rust Rand Book (em inglês): compara os geradores, incluindo quais servem para segurança.
- Reproducibility — The Rust Rand Book (em inglês): como manter os resultados com seed estáveis entre versões e plataformas.