Truques da biblioteca padrão em Rust: atalhos para o dia a dia
Toda cozinha tem uma gaveta de ferramentas pequenas: descascador, abridor de garrafa, espremedor de alho. Dá para fazer tudo com uma faca grande, mas as ferramentas pequenas deixam o trabalho mais rápido e mais seguro. A biblioteca padrão do Rust também tem uma gaveta assim.
Esta página é um passeio por essa gaveta. Cada truque é curto: uma imagem do dia a dia, um exemplo que você pode rodar e uma linha “use quando…” para você saber a hora de pegar aquela ferramenta. Você não precisa aprender tudo de uma vez. Dê uma olhada geral agora e volte quando encontrar no seu código um trecho que parece desajeitado.
Tirando um valor de dentro de um &mut: mem::take, replace, swap
Seção intitulada “Tirando um valor de dentro de um &mut: mem::take, replace, swap”Imagine uma lixeira com um saco dentro. Você pode tirar o saco cheio, mas só se colocar um saco novo logo em seguida. A lixeira nunca fica sem saco.
O Rust tem a mesma regra. Se você só tem &mut self, não pode simplesmente mover um campo para fora, porque isso deixaria um buraco na struct. O compilador te impede com o erro E0507. As funções de std::mem resolvem isso deixando sempre alguma coisa no lugar:
mem::take(&mut x)pega o valor e deixa o valor padrão do tipo (umVecvazio, umaStringvazia,0,None).mem::replace(&mut x, novo)colocanovono lugar e te devolve o valor antigo.Option::take()pega o que está dentro de umOptione deixaNone.
use std::mem;
struct Inbox { unread: Vec<String>, status: String, draft: Option<String>,}
impl Inbox { // Entrega todas as mensagens não lidas e deixa um Vec vazio no lugar. fn read_all(&mut self) -> Vec<String> { mem::take(&mut self.unread) }
// Coloca um status novo e devolve o antigo. fn set_status(&mut self, new: &str) -> String { mem::replace(&mut self.status, new.to_string()) }
// Tira o rascunho (se houver) e deixa None. fn send_draft(&mut self) -> Option<String> { self.draft.take() }}
fn main() { let mut inbox = Inbox { unread: vec!["Almoço?".to_string(), "Fatura #42".to_string()], status: "disponível".to_string(), draft: Some("Te vejo às 6".to_string()), };
let messages = inbox.read_all(); println!("lidas {:?}, ainda não lidas: {:?}", messages, inbox.unread);
let old = inbox.set_status("em reunião"); println!("o status era '{old}', agora é '{}'", inbox.status);
println!("enviado: {:?}", inbox.send_draft()); println!("enviado de novo: {:?}", inbox.send_draft());}Saída:
lidas ["Almoço?", "Fatura #42"], ainda não lidas: []o status era 'disponível', agora é 'em reunião'enviado: Some("Te vejo às 6")enviado de novo: NoneNão tem nenhum clone() aqui: as mensagens e o status antigo são movidos para fora, não copiados. É por isso que essas funções são ao mesmo tempo seguras e baratas.
Duas parentes completam o conjunto. mem::swap(&mut a, &mut b) troca dois valores de lugar, como trocar dois pratos na mesa. Option::replace(novo) coloca um valor novo dentro de um Option e devolve o antigo.
use std::mem;
fn main() { // Uma lista vai enchendo enquanto a outra está sendo processada. let mut filling = vec!["pedido 1", "pedido 2"]; let mut processing: Vec<&str> = Vec::new();
mem::swap(&mut filling, &mut processing); println!("processando {processing:?}, recebendo {filling:?}");
// Option::replace: coloca um valor novo e devolve o antigo. let mut now_playing = Some("Música A"); let previous = now_playing.replace("Música B"); println!("era {previous:?}, agora {now_playing:?}");}Saída:
processando ["pedido 1", "pedido 2"], recebendo []era Some("Música A"), agora Some("Música B")Use quando você só tem acesso &mut, mas precisa ser dono do valor que está lá dentro.
Slices: windows, chunks e companhia
Seção intitulada “Slices: windows, chunks e companhia”Pense em windows(2) como uma lupa deslizando por uma régua: ela mostra duas marcas por vez e depois anda um passo. chunks(3) é como guardar ovos em caixas de três: a última caixa pode ficar só pela metade. chunks_exact(3) só te dá caixas cheias e deixa os ovos que sobraram separados em .remainder().
Esses métodos funcionam em qualquer slice (fatia), então funcionam também em arrays e vetores.
fn main() { let temps = [12, 15, 14, 19, 21];
// windows(2): cada par de vizinhos, com sobreposição for pair in temps.windows(2) { println!("{} -> {}: variação {}", pair[0], pair[1], pair[1] - pair[0]); }
// chunks(3): lotes de 3; o último pode ser menor let emails = ["[email protected]", "[email protected]", "[email protected]", "[email protected]", "[email protected]"]; for batch in emails.chunks(3) { println!("enviando para {batch:?}"); }
// chunks_exact(2): só lotes completos; o resto espera em remainder() let seats = [1, 2, 3, 4, 5]; let pairs = seats.chunks_exact(2); let leftover = pairs.remainder(); for pair in pairs { println!("par {pair:?}"); } println!("sobrou: {leftover:?}");}Saída:
12 -> 15: variação 315 -> 14: variação -114 -> 19: variação 519 -> 21: variação 2enviando para ["[email protected]", "[email protected]", "[email protected]"]enviando para ["[email protected]", "[email protected]"]par [1, 2]par [3, 4]sobrou: [5]Mais alguns helpers de slice respondem perguntas comuns sem precisar de laço. split_first() te dá o primeiro item e o resto, como uma professora que lê o primeiro nome da lista de chamada e passa o resto da lista para um ajudante. split_last() faz o mesmo a partir do fim.
fn main() { let command = ["git", "commit", "-m", "corrige erro de digitação"];
// split_first: o primeiro item e uma slice com o resto if let Some((program, args)) = command.split_first() { println!("programa: {program}, argumentos: {args:?}"); }
// split_last: o último item e tudo o que vem antes dele if let Some((message, _rest)) = command.split_last() { println!("mensagem: {message}"); }
println!("é git? {}", command.starts_with(&["git"])); println!("tem -m? {}", command.contains(&"-m"));
// iter().rev(): percorre de trás para frente sem mudar a slice let history = ["abrir", "editar", "salvar"]; let undo_order: Vec<_> = history.iter().rev().collect(); println!("ordem para desfazer: {undo_order:?}");}Saída:
programa: git, argumentos: ["commit", "-m", "corrige erro de digitação"]mensagem: corrige erro de digitaçãoé git? truetem -m? trueordem para desfazer: ["salvar", "editar", "abrir"]split_first e split_last retornam um Option, porque uma slice vazia não tem primeiro item. É por isso que usamos if let.
Use quando você precisa de vizinhos (windows), de lotes (chunks) ou de “o primeiro e o resto” (split_first).
Helpers de iterador que você vai usar o tempo todo
Seção intitulada “Helpers de iterador que você vai usar o tempo todo”A página de Iteradores mostra map, filter, sum e collect. Aqui estão as próximas ferramentas da caixa.
peekable: olhe antes de pegar
Seção intitulada “peekable: olhe antes de pegar”Imagine que você está distribuindo cartas. Às vezes você quer olhar a próxima carta antes de decidir se vai comprá-la. .peekable() te dá um iterador que sabe fazer isso: .peek() mostra o próximo item sem tirá-lo, e .next_if(...) só pega o item se ele passar num teste. É exatamente disso que pequenos parsers (analisadores) precisam.
fn main() { let input = "12+345-6"; let mut chars = input.chars().peekable(); let mut tokens: Vec<String> = Vec::new();
while let Some(c) = chars.next() { if c.is_ascii_digit() { let mut number = c.to_string(); // Pega o próximo caractere só enquanto ele também for um dígito. while let Some(d) = chars.next_if(|next| next.is_ascii_digit()) { number.push(d); } tokens.push(number); } else { tokens.push(c.to_string()); } }
println!("{tokens:?}");}Saída:
["12", "+", "345", "-", "6"]Sem peekable, você leria o + enquanto procurava mais dígitos, e aí ele já teria sumido.
Use quando o próximo item decide o que você faz com o item atual.
Percorrendo listas: zip, enumerate, step_by, take_while, skip_while, chain
Seção intitulada “Percorrendo listas: zip, enumerate, step_by, take_while, skip_while, chain”| Método | Pense nele como | Use quando… |
|---|---|---|
zip |
um zíper juntando os dois lados, dente por dente | você tem duas listas que andam juntas |
enumerate |
numerar as linhas de uma lista | você precisa do índice além do item |
step_by(n) |
subir a escada de dois em dois (ou de n em n) degraus | você quer um item a cada n |
take_while |
comer balas até chegar numa que você não gosta | você quer o começo da lista, até o primeiro “não” |
skip_while |
pular os comerciais até o programa começar | você quer o resto, a partir do primeiro “não” |
chain |
engatar dois vagões de trem | você quer uma lista depois da outra |
fn main() { let names = ["Ana", "Ben", "Chen"]; let scores = [88, 92, 75];
// zip junta duas listas em pares; enumerate adiciona um contador que começa em 0 for (i, (name, score)) in names.iter().zip(scores.iter()).enumerate() { println!("{}. {name}: {score}", i + 1); }
// step_by: de 15 em 15 minutos dentro da hora let quarters: Vec<u32> = (0..60).step_by(15).collect(); println!("quartos de hora: {quarters:?}");
// into_iter() num array dá os próprios números let queue = [3, 5, 8, 12, 4]; let small: Vec<i32> = queue.into_iter().take_while(|n| *n < 10).collect(); let rest: Vec<i32> = queue.into_iter().skip_while(|n| *n < 10).collect(); println!("antes do primeiro grande: {small:?}, dali em diante: {rest:?}");
// chain: uma lista depois da outra let morning = ["e-mail", "daily"]; let afternoon = ["revisão", "deploy"]; let day: Vec<_> = morning.iter().chain(afternoon.iter()).collect(); println!("{day:?}");}Saída:
1. Ana: 882. Ben: 923. Chen: 75quartos de hora: [0, 15, 30, 45]antes do primeiro grande: [3, 5, 8], dali em diante: [12, 4]["e-mail", "daily", "revisão", "deploy"]Repare que take_while para no 12 de vez. O 4 que vem depois é pequeno, mas nem chega a ser testado. Essa é a diferença para o filter, que olha todos os itens.
Respondendo perguntas: fold, position, find_map, partition, flat_map
Seção intitulada “Respondendo perguntas: fold, position, find_map, partition, flat_map”| Método | Pense nele como | Use quando… |
|---|---|---|
sum |
a tecla de mais da calculadora | você só quer o total |
fold |
uma bola de neve rolando morro abaixo, crescendo a cada passo | você monta um resultado do seu jeito |
position |
“em qual cadeira o Ben está sentado?” | você precisa do índice do primeiro item que combina |
find_map |
testar chaves até uma abrir a porta | você tenta algo em cada item e fica com o primeiro sucesso |
partition |
separar a roupa suja em dois cestos | você divide uma lista em duas com uma pergunta de sim ou não |
flat_map |
esvaziar várias sacolas na mesma mesa | cada item vira vários itens |
fn main() { let orders = [12, 40, 7, 55, 30];
// sum: só soma tudo let total: i32 = orders.iter().sum();
// fold: soma do seu jeito (aqui: cada pedido paga também 2 reais de taxa) let with_fees = orders.iter().fold(0, |acc, price| acc + price + 2); println!("total {total}, com taxas {with_fees}");
// position: o índice do primeiro item que combina println!("o primeiro pedido acima de 50 está no índice {:?}", orders.iter().position(|p| *p > 50));
// partition: dois Vecs, um para o "sim" e outro para o "não" let (big, small): (Vec<i32>, Vec<i32>) = orders.into_iter().partition(|p| *p >= 30); println!("grandes {big:?}, pequenos {small:?}");
// find_map: tenta converter cada um e fica com o primeiro que der certo let inputs = ["abc", "", "42", "7"]; let first_number = inputs.iter().find_map(|s| s.parse::<i32>().ok()); println!("primeiro número: {first_number:?}");
// flat_map: cada linha vira várias palavras, todas numa lista só let lines = ["comprar leite", "ligar para mamãe"]; let words: Vec<&str> = lines.iter().flat_map(|line| line.split(' ')).collect(); println!("{words:?}");}Saída:
total 144, com taxas 154o primeiro pedido acima de 50 está no índice Some(3)grandes [40, 55, 30], pequenos [12, 7]primeiro número: Some(42)["comprar", "leite", "ligar", "para", "mamãe"]fold recebe um valor inicial (0) e uma closure (função anônima) que recebe “o resultado até agora” e o próximo item. Se você só precisa de um total simples, sum diz isso com mais clareza. Use fold quando a regra for sua.
Arrumando um Vec no próprio lugar: dedup e retain
Seção intitulada “Arrumando um Vec no próprio lugar: dedup e retain”Esses dois são métodos de Vec, não de iterador, mas moram na mesma gaveta. dedup é como um DJ que nunca toca a mesma música duas vezes seguidas (a música pode voltar mais tarde). retain é o segurança da porta: só os itens que passam no teste ficam lá dentro.
fn main() { // dedup remove repetições que estão uma do lado da outra let mut clicks = vec!["início", "início", "loja", "loja", "loja", "carrinho", "início"]; clicks.dedup(); println!("{clicks:?}");
// retain mantém só os itens que passam no teste let mut cart = vec![("maçã", 3), ("pão", 0), ("leite", 1), ("ovos", 0)]; cart.retain(|(_, qty)| *qty > 0); println!("{cart:?}");}Saída:
["início", "loja", "carrinho", "início"][("maçã", 3), ("leite", 1)]O último "início" sobreviveu porque não estava do lado de outro "início". Para remover todas as repetições, ordene antes, como mostra a página de Ordenação.
Use quando você quer mudar um Vec sem montar um novo.
Facilidades de Option e Result
Seção intitulada “Facilidades de Option e Result”Um Option é como uma encomenda que pode ou não ter chegado. Você poderia abrir cada encomenda com um match completo, mas na maioria das vezes um helper de uma palavra diz a mesma coisa. A página de Option mostra unwrap_or e match; aqui estão os próximos helpers.
fn main() { let nickname: Option<String> = None; let age: Option<u32> = Some(17); let width: Option<u32> = Some(3); let height: Option<u32> = Some(4);
// unwrap_or_default: o valor, ou o padrão do tipo ("", 0, Vec vazio...) println!("apelido: '{}'", nickname.unwrap_or_default());
// map_or: um valor reserva se for None; senão, transforma o valor println!("{}", age.map_or("idade desconhecida".to_string(), |a| format!("{a} anos")));
// filter: só fica com o valor se ele passar num teste println!("idade adulta: {:?}", age.filter(|a| *a >= 18));
// zip: Some só se os DOIS forem Some println!("área: {:?}", width.zip(height).map(|(w, h)| w * h));
// and_then: encadeia um passo que também pode dar None let good = Some("42").and_then(|s| s.parse::<u32>().ok()); let bad = Some("quarenta").and_then(|s| s.parse::<u32>().ok()); println!("convertidos: {good:?} e {bad:?}");}Saída:
apelido: ''17 anosidade adulta: Noneárea: Some(12)convertidos: Some(42) e NoneDentro de uma função, o ? também funciona com Option: se o valor for None, a função retorna None na hora. Numa função que retorna Result, transforme o Option em Result primeiro com ok_or(erro) ou ok_or_else(|| erro). Prefira ok_or_else quando montar o erro dá trabalho, porque ele só roda quando é necessário.
struct Employee { name: String, manager: Option<String>,}
// `?` num Option: se for None, retorna None na hora.fn manager_initial(e: &Employee) -> Option<char> { let manager = e.manager.as_ref()?; manager.chars().next()}
// ok_or_else transforma None num erro, então o `?` funciona numa função que retorna Result.fn port_from(setting: Option<&str>) -> Result<u16, String> { let raw = setting.ok_or_else(|| "PORT não está definida".to_string())?; raw.parse::<u16>().map_err(|e| e.to_string())}
fn main() { let ana = Employee { name: "Ana".to_string(), manager: Some("Grace".to_string()) }; let grace = Employee { name: "Grace".to_string(), manager: None }; println!("{}: {:?}", ana.name, manager_initial(&ana)); println!("{}: {:?}", grace.name, manager_initial(&grace));
println!("{:?}", port_from(Some("8080"))); println!("{:?}", port_from(None)); println!("{:?}", port_from(Some("oitenta")));
// transpose: Option<Result<..>> vira Result<Option<..>> let retries_setting: Option<&str> = Some("3"); let retries: Result<Option<u8>, _> = retries_setting.map(|s| s.parse::<u8>()).transpose(); println!("{retries:?}");}Saída:
Ana: Some('G')Grace: NoneOk(8080)Err("PORT não está definida")Err("invalid digit found in string")Ok(Some(3))A mensagem invalid digit found in string (“dígito inválido encontrado na string”) vem do próprio Rust, por isso aparece em inglês. transpose é raro, mas é a resposta elegante para “tenho uma configuração opcional, e convertê-la pode falhar”. Com ele, você pode colocar ? no resultado e continuar com o Option.
Use quando um match num Option tem um braço interessante e outro sem graça.
A entry API do HashMap
Seção intitulada “A entry API do HashMap”Pense no guarda-volumes de um teatro. Você diz um nome, e o atendente encontra o gancho certo ou pendura um novo, e depois te entrega. Uma viagem só, não duas. Isso é o .entry(chave). A página de HashMaps mostra entry(chave).or_insert(valor), o contador clássico. Vale a pena conhecer mais duas variações:
or_insert_with(f)só cria o valor padrão quando a chave não existe.or_default()faz o mesmo com o padrão do tipo.and_modify(f)altera o valor se a chave existir; encadeie umor_insertdepois dele para o caso de “chave nova”.
use std::collections::HashMap;
fn main() { let sales = [("maçãs", 3), ("peras", 2), ("maçãs", 5), ("ameixas", 1), ("peras", 4)];
// and_modify + or_insert: atualiza se existir, insere se não existir let mut totals: HashMap<&str, i32> = HashMap::new(); for (fruit, qty) in sales { totals.entry(fruit).and_modify(|t| *t += qty).or_insert(qty); }
// or_insert_with: cria o Vec vazio só quando for preciso let mut orders: HashMap<&str, Vec<i32>> = HashMap::new(); for (fruit, qty) in sales { orders.entry(fruit).or_insert_with(Vec::new).push(qty); }
// A ordem do HashMap é aleatória, então ordenamos as chaves antes de imprimir let mut fruits: Vec<&str> = totals.keys().copied().collect(); fruits.sort(); for fruit in fruits { println!("{fruit}: total {}, pedidos {:?}", totals[fruit], orders[fruit]); }}Saída:
ameixas: total 1, pedidos [1]maçãs: total 8, pedidos [3, 5]peras: total 6, pedidos [2, 4]Use quando você escreveria “se a chave existe, altere; senão, insira”, o que procura a chave duas vezes.
O tipo never !: código que nunca volta
Seção intitulada “O tipo never !: código que nunca volta”Algumas ruas são de mão única: depois que você entra, nunca mais sai por ali. Em Rust, uma expressão que nunca termina tem o tipo never (tipo “nunca”), escrito !. panic!, return, break, continue, um loop infinito e std::process::exit têm esse tipo.
Como um valor do tipo ! nunca chega a existir de verdade, o compilador deixa ele ocupar o lugar de qualquer tipo. É por isso que um match pode ter um braço que dá um i32 e outro que faz return: o segundo braço nunca produz um valor, então não tem como bagunçar os tipos.
use std::process;
// `-> !` quer dizer "esta função nunca retorna"fn quit(message: &str) -> ! { println!("{message}"); process::exit(0)}
fn level_points(level: &str) -> u32 { let points: u32 = match level { "fácil" => 10, "difícil" => 30, _ => return 0, // `return` tem o tipo `!`, então cabe onde se espera um u32 }; points * 2}
fn main() { println!("fácil: {}, difícil: {}, estranho: {}", level_points("fácil"), level_points("difícil"), level_points("estranho"));
let mut total = 0; for input in ["4", "opa", "6"] { let n: i32 = match input.parse() { Ok(n) => n, Err(_) => continue, // `continue` também é `!` }; total += n; } println!("total: {total}");
let config_file: Option<&str> = None; let config = match config_file { Some(path) => path, None => quit("nenhum arquivo de configuração encontrado, parando"), }; println!("carregado {config}"); // nunca é impresso}Saída:
fácil: 20, difícil: 60, estranho: 0total: 10nenhum arquivo de configuração encontrado, parandoUm aviso: process::exit encerra o programa na hora, sem rodar o código de limpeza (os destrutores) dos valores que ainda estão vivos. Em programas de verdade, retornar de main costuma ser a opção mais gentil.
Duas macros usam ! para marcar lugar enquanto você esboça o código. todo!() quer dizer “vou escrever isso depois”. unimplemented!() quer dizer “isso não é suportado aqui”. As duas causam um panic se forem alcançadas, mas deixam o resto do programa compilar:
fn export_pdf(report: &str) -> Vec<u8> { todo!("a exportação para PDF fica para a v2")}Se você chamar essa função, recebe not yet implemented: a exportação para PDF fica para a v2 (“ainda não implementado”). Com unimplemented!("exportação para PDF"), a mensagem é not implemented: exportação para PDF (“não implementado”). O começo da mensagem vem do próprio Rust, por isso fica em inglês.
Use quando uma função deve parar o programa, ou um braço deve sair mais cedo em vez de dar um valor.
Pequenas facilidades do dia a dia
Seção intitulada “Pequenas facilidades do dia a dia”Estas são as ferramentas de bolso: minúsculas, mas você vai usá-las toda semana.
matches!(valor, padrão)responde “isso se encaixa no padrão?” comtrueoufalse(mais na página de Pattern Matching).std::cmp::min/maxescolhem o menor ou o maior de dois valores, e.clamp(mínimo, máximo)mantém um valor dentro de uma faixa, como um botão de volume que para no 100..abs_diff(outro)dá a distância entre dois números. Ela é sempre positiva e nunca dá overflow, nem mesmo em tipos sem sinal.dbg!(expr)imprime o arquivo, a linha, a expressão e o valor, e depois devolve o valor, então você pode colocá-la em volta de qualquer coisa.assert!(condição)causa um panic se a condição for falsa.debug_assert!faz o mesmo, mas só em builds de debug, então não custa nada nos builds de release.
use std::cmp::{max, min};
enum Order { Pending, Shipped, Delivered, Cancelled,}
fn main() { let orders = [Order::Pending, Order::Delivered, Order::Shipped, Order::Cancelled, Order::Pending];
// matches!: uma checagem de padrão com resposta sim ou não; | quer dizer "ou" let open = orders.iter().filter(|o| matches!(o, Order::Pending | Order::Shipped)).count(); println!("pedidos em aberto: {open}");
// min, max e clamp println!("mín {}, máx {}", min(7, 3), max(7, 3)); let volume = 140; let safe = volume.clamp(0, 100); println!("volume {volume} limitado a {safe}");
// abs_diff: a distância entre os dois, nunca negativa let (guess, secret): (u32, u32) = (30, 42); println!("você está a {} de distância", guess.abs_diff(secret));
// dbg! imprime no stderr e devolve o valor let doubled = dbg!(safe * 2);
// assert! para o programa se isto um dia for falso assert!(doubled <= 200, "volume dobrado alto demais: {doubled}"); debug_assert!(safe <= 100); // só é verificado em builds de debug println!("todas as verificações passaram");}Saída:
pedidos em aberto: 3mín 3, máx 7volume 140 limitado a 100você está a 12 de distânciatodas as verificações passaramA linha do dbg! não aparece na saída acima, porque o dbg! escreve no stderr (saída de erro), não no stdout. No Playground, ela aparece no painel “Standard Error”:
[src/main.rs:28:19] safe * 2 = 200É também por isso que dá para espalhar dbg! pelo código sem medo enquanto você depura: ele não se mistura com a saída real do programa. Só lembre de tirá-lo antes de publicar.
A ordenação tem as suas próprias ferramentinhas. A página de Ordenação fala delas em detalhe; aqui vai a versão curta. sort_unstable é um pouco mais rápido que sort e serve muito bem para números simples. sort_by_key ordena por um campo. binary_search encontra um item em dados ordenados bem rápido, como abrir o dicionário no meio em vez de ler a partir da primeira página.
struct Player { name: &'static str, score: u32,}
fn main() { // sort_unstable: um pouco mais rápido; itens iguais podem trocar de lugar let mut ids = vec![42, 7, 19, 3, 25]; ids.sort_unstable(); println!("{ids:?}");
// binary_search: Ok(índice) se achar, Err(índice) onde o item entraria println!("procurar 19: {:?}", ids.binary_search(&19)); println!("procurar 20: {:?}", ids.binary_search(&20));
// sort_by_key: ordena por um campo let mut players = vec![ Player { name: "Ana", score: 120 }, Player { name: "Ben", score: 95 }, Player { name: "Chen", score: 150 }, ]; players.sort_by_key(|p| p.score); let names: Vec<&str> = players.iter().map(|p| p.name).collect(); println!("do menor para o maior: {names:?}");}Saída:
[3, 7, 19, 25, 42]procurar 19: Ok(2)procurar 20: Err(3)do menor para o maior: ["Ben", "Ana", "Chen"]Err(3) não é uma falha com que você precise se preocupar. Ele diz que o 20 não está na lista e que o índice 3 é onde você poderia inseri-lo para manter a lista ordenada.
Use quando você se pegar escrevendo um if ou um laço pequeno que uma dessas ferramentas já faz.
Erros comuns
Seção intitulada “Erros comuns”Usar mem::take num tipo sem Default. O mem::take precisa deixar alguma coisa no lugar, então ele precisa saber o que “vazio” significa para aquele tipo:
use std::mem;
struct Connection { host: String,}
struct Pool { active: Connection,}
impl Pool { fn reset(&mut self) -> Connection { mem::take(&mut self.active) // ❌ o que seria uma Connection "vazia"? }}error[E0277]: the trait bound `Connection: Default` is not satisfied --> src/main.rs:13:19 |13 | mem::take(&mut self.active) // ❌ o que seria uma Connection "vazia"? | --------- ^^^^^^^^^^^^^^^^ the trait `Default` is not implemented for `Connection` | | | required by a bound introduced by this call |help: consider annotating `Connection` with `#[derive(Default)]`O compilador diz que Connection não implementa a trait (característica) Default e sugere adicionar #[derive(Default)]. Corrija escolhendo você mesmo o valor substituto com mem::replace(&mut self.active, Connection { host: "db2".to_string() }), ou guardando o campo como Option<Connection> e usando self.active.take(). Só adicione #[derive(Default)] se um valor “vazio” fizer sentido de verdade para o tipo.
Usar ? num Option dentro de uma função que retorna Result. Os dois não se misturam automaticamente:
fn port_from(setting: Option<&str>) -> Result<u16, String> { let raw = setting?; // ❌ None não tem mensagem de erro para retornar raw.parse::<u16>().map_err(|e| e.to_string())}error[E0277]: the `?` operator can only be used on `Result`s, not `Option`s, in a function that returns `Result` --> src/main.rs:2:22 |1 | fn port_from(setting: Option<&str>) -> Result<u16, String> { | ---------------------------------------------------------- this function returns a `Result`2 | let raw = setting?; // ❌ None não tem mensagem de erro para retornar | ^ use `.ok_or(...)?` to provide an error compatible with `Result<u16, String>`O próprio compilador já te dá a solução: setting.ok_or("PORT não está definida".to_string())?, ou ok_or_else, como no exemplo lá em cima. Veja o E0277 para outras formas desse erro.
Esperar que dedup ou binary_search funcionem com dados fora de ordem. O dedup só remove repetições que estão uma do lado da outra, e o binary_search em dados fora de ordem dá uma resposta sem sentido. Nenhum dos dois gera erro de compilação, então é fácil não perceber. Ordene antes.
Esquecer as sobras do chunks_exact. Um laço for sobre chunks_exact(3) pula em silêncio os últimos 1 ou 2 itens. Se eles importam, trate o .remainder() ou use chunks no lugar.
Prática
Seção intitulada “Prática”1. Variação diária de preços
Seção intitulada “1. Variação diária de preços”Você tem os preços [100, 104, 98, 110, 107]. Monte um Vec com a variação de cada dia para o seguinte, imprima esse Vec e depois imprima a maior alta. Dica: windows(2) te dá cada par como uma pequena slice w, então a variação é w[1] - w[0]. .iter().max() encontra o maior valor.
Ver solução
fn main() { let prices = [100, 104, 98, 110, 107];
let changes: Vec<i32> = prices.windows(2).map(|w| w[1] - w[0]).collect(); println!("variações: {changes:?}");
let biggest_rise = changes.iter().max(); println!("maior alta: {biggest_rise:?}");}Saída:
variações: [4, -6, 12, -3]maior alta: Some(12)Cinco preços dão quatro variações, porque windows(2) forma um par a cada passo. max() retorna um Option, já que uma lista vazia não tem maior valor.
2. Esvaziando a fila da impressora
Seção intitulada “2. Esvaziando a fila da impressora”Escreva uma struct Printer com um campo queue: Vec<String>. Dê a ela um método add(&mut self, file: &str) e um método flush(&mut self) -> Vec<String> que devolve todos os arquivos da fila e deixa a fila vazia. Adicione dois arquivos, chame flush e imprima o que saiu da fila e quantos arquivos sobraram. Dica: flush só tem &mut self, então use std::mem::take.
Ver solução
use std::mem;
struct Printer { queue: Vec<String>,}
impl Printer { fn add(&mut self, file: &str) { self.queue.push(file.to_string()); }
fn flush(&mut self) -> Vec<String> { mem::take(&mut self.queue) }}
fn main() { let mut printer = Printer { queue: Vec::new() }; printer.add("relatorio.pdf"); printer.add("foto.png");
let jobs = printer.flush(); println!("imprimindo {} arquivos: {jobs:?}", jobs.len()); println!("restam na fila: {}", printer.queue.len());}Saída:
imprimindo 2 arquivos: ["relatorio.pdf", "foto.png"]restam na fila: 0Escrever só self.queue dentro de flush falharia com o erro E0507. O mem::take move o Vec para fora e deixa um vazio no lugar, sem copiar nada.
3. A primeira porta válida
Seção intitulada “3. A primeira porta válida”Escreva uma função first_port(candidates: &[&str]) -> Option<u16> que retorna a primeira string que pode ser convertida em u16. Chame a função com ["", "abc", "70000", "8080", "9090"] e com ["nenhuma", "-1"], e imprima a porta, ou 3000 se não houver nenhuma. Dica: find_map com s.parse::<u16>().ok(), e depois unwrap_or(3000).
Ver solução
fn first_port(candidates: &[&str]) -> Option<u16> { candidates.iter().find_map(|s| s.parse::<u16>().ok())}
fn main() { let from_config = ["", "abc", "70000", "8080", "9090"]; let all_bad = ["nenhuma", "-1"];
println!("usando a porta {}", first_port(&from_config).unwrap_or(3000)); println!("usando a porta {}", first_port(&all_bad).unwrap_or(3000));}Saída:
usando a porta 8080usando a porta 3000"70000" parece um número, mas não cabe num u16 (o maior é 65535), então a conversão falha e o find_map segue em frente. Ele para no "8080" e nem chega a olhar o "9090".
mem::take,mem::replaceeOption::taketiram um valor de dentro de um&mutdeixando alguma coisa no lugar;mem::swaptroca dois valores.- Em slices,
windowsdá vizinhos sobrepostos,chunksdá lotes, esplit_first/split_lastdão “um item e o resto”. peekabledeixa você olhar adiante;zip,enumerate,chain,fold,find_mapepartitionsubstituem muitos laços escritos à mão.- Helpers de
Optioncomounwrap_or_default,map_or,filtereok_orte poupam de escrever pequenosmatch, e o?também funciona comOption. entry()comor_insert_withouand_modifyatualiza umHashMapcom uma única busca.- O tipo never
!pertence ao código que nunca termina (panic!,return,continue,process::exit), por isso ele cabe em qualquer lugar onde se espera um valor.
Teste o que aprendeu
Seção intitulada “Teste o que aprendeu”Teste o que aprendeu
1. O que mem::take(&mut self.items) deixa no lugar quando items é um Vec?
2. O que [1, 2, 3, 4].windows(2) te dá?
3. Por que let x: i32 = match s { "a" => 1, _ => return }; compila?
4. Sua função retorna um Result, mas você tem um Option. O que você coloca antes do ?
Pontuação: 0 / 4
Para saber mais
Seção intitulada “Para saber mais”std::mem— documentação da biblioteca padrão (em inglês):take,replace,swape os outros helpers de memória, com exemplos.- Métodos de slice — documentação da biblioteca padrão (em inglês): todos os métodos de slice, incluindo
windows,chunks,split_firstebinary_search. - Trait
Iterator— documentação da biblioteca padrão (em inglês): a lista completa de métodos de iterador, dezipepeekableatépartitionefind_map. Option— documentação da biblioteca padrão (em inglês): todos os helpers deOption, agrupados pelo que fazem.- Diverging functions — Rust by Example (em inglês): exemplos curtos de funções que retornam
!e por que isso é útil.