Pular para o conteúdo

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 (um Vec vazio, uma String vazia, 0, None).
  • mem::replace(&mut x, novo) coloca novo no lugar e te devolve o valor antigo.
  • Option::take() pega o que está dentro de um Option e deixa None.
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: None
▶ Experimente você mesmo

Nã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")
▶ Experimente você mesmo

Use quando você só tem acesso &mut, mas precisa ser dono do valor que está lá dentro.

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
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 3
15 -> 14: variação -1
14 -> 19: variação 5
19 -> 21: variação 2
par [1, 2]
par [3, 4]
sobrou: [5]
▶ Experimente você mesmo

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? true
tem -m? true
ordem para desfazer: ["salvar", "editar", "abrir"]
▶ Experimente você mesmo

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.

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"]
▶ Experimente você mesmo

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: 88
2. Ben: 92
3. Chen: 75
quartos de hora: [0, 15, 30, 45]
antes do primeiro grande: [3, 5, 8], dali em diante: [12, 4]
["e-mail", "daily", "revisão", "deploy"]
▶ Experimente você mesmo

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 154
o 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"]
▶ Experimente você mesmo

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)]
▶ Experimente você mesmo

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.

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 anos
idade adulta: None
área: Some(12)
convertidos: Some(42) e None
▶ Experimente você mesmo

Dentro 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: None
Ok(8080)
Err("PORT não está definida")
Err("invalid digit found in string")
Ok(Some(3))
▶ Experimente você mesmo

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.

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 um or_insert depois 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]
▶ Experimente você mesmo

Use quando você escreveria “se a chave existe, altere; senão, insira”, o que procura a chave duas vezes.

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: 0
total: 10
nenhum arquivo de configuração encontrado, parando
▶ Experimente você mesmo

Um 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.

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?” com true ou false (mais na página de Pattern Matching).
  • std::cmp::min / max escolhem 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: 3
mín 3, máx 7
volume 140 limitado a 100
você está a 12 de distância
todas as verificações passaram
▶ Experimente você mesmo

A 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"]
▶ Experimente você mesmo

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.

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.

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)
▶ Experimente você mesmo

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.

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: 0
▶ Experimente você mesmo

Escrever 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.

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 8080
usando a porta 3000
▶ Experimente você mesmo

"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::replace e Option::take tiram um valor de dentro de um &mut deixando alguma coisa no lugar; mem::swap troca dois valores.
  • Em slices, windows dá vizinhos sobrepostos, chunks dá lotes, e split_first / split_last dão “um item e o resto”.
  • peekable deixa você olhar adiante; zip, enumerate, chain, fold, find_map e partition substituem muitos laços escritos à mão.
  • Helpers de Option como unwrap_or_default, map_or, filter e ok_or te poupam de escrever pequenos match, e o ? também funciona com Option.
  • entry() com or_insert_with ou and_modify atualiza um HashMap com 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

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