Option em Rust
O Rust não tem null. Se um valor pode estar ausente, isso fica escrito no próprio tipo, usando Option. No começo parece cerimônia demais, mas isso elimina uma categoria inteira de bugs: a famosa “referência nula”, que já causou incontáveis travamentos em outras linguagens.
Como é o Option
Seção intitulada “Como é o Option”Option é um enum com duas variantes:
enum Option<T> { Some(T), // existe um valor None, // não há valor}Na verdade, você já esbarrou nele: tanto o .get() de um vetor quanto o .get() de um HashMap devolvem um Option.
Criando um Option
Seção intitulada “Criando um Option”fn main() { let has_value: Option<i32> = Some(5); let no_value: Option<i32> = None;
println!("{:?}", has_value); println!("{:?}", no_value);}Saída:
Some(5)NoneTratando um Option com match
Seção intitulada “Tratando um Option com match”A forma segura de tirar o valor de dentro é usar match. O Rust obriga você a tratar os dois casos, então não tem como esquecer, sem querer, o caso em que o valor “não existe”:
fn main() { let age: Option<i32> = Some(30);
match age { Some(value) => println!("A idade é {value}"), None => println!("Nenhuma idade informada"), }}Um exemplo mais realista
Seção intitulada “Um exemplo mais realista”fn find_user(id: u32) -> Option<&'static str> { if id == 1 { Some("Alice") } else { None }}
fn main() { match find_user(1) { Some(name) => println!("Encontrado: {name}"), None => println!("Usuário não encontrado"), }
match find_user(99) { Some(name) => println!("Encontrado: {name}"), None => println!("Usuário não encontrado"), }}Saída:
Encontrado: AliceUsuário não encontradoEm vez de devolver null ou lançar uma exceção quando o usuário não é encontrado, o próprio tipo de retorno da função avisa a quem chama: “talvez eu não encontre nada”. E o compilador não deixa ninguém ignorar essa possibilidade.
Atalhos: unwrap_or e unwrap
Seção intitulada “Atalhos: unwrap_or e unwrap”Em casos simples, um match completo pode parecer exagero. Existem alguns atalhos:
fn main() { let age: Option<i32> = None;
let value = age.unwrap_or(0); // usa 0 se não houver valor println!("{value}");}Também existe o .unwrap(), que entrega o valor diretamente, mas causa um panic (o programa para com erro) se ele for None:
let age: Option<i32> = Some(30);let value = age.unwrap(); // tudo bem aqui, porque age é SomePrática
Seção intitulada “Prática”1. O primeiro número par
Seção intitulada “1. O primeiro número par”Escreva fn first_even(numbers: &[i32]) -> Option<i32>, que devolve o primeiro número par da slice, ou None se não houver nenhum. Imprima o resultado para [3, 7, 8, 10] usando match e para [1, 3, 5] usando .unwrap_or(0). Dica: percorra a slice com um laço e faça return Some(n) assim que n % 2 == 0; depois do laço, devolva None.
Ver solução
fn first_even(numbers: &[i32]) -> Option<i32> { for &n in numbers { if n % 2 == 0 { return Some(n); } } None}
fn main() { match first_even(&[3, 7, 8, 10]) { Some(n) => println!("Primeiro par: {n}"), None => println!("Nenhum número par"), }
let fallback = first_even(&[1, 3, 5]).unwrap_or(0); println!("Com valor padrão: {fallback}");}Saída:
Primeiro par: 8Com valor padrão: 02. Consultando uma configuração
Seção intitulada “2. Consultando uma configuração”Crie um HashMap de configurações que contenha apenas "volume" com o valor 80. Depois, percorra as chaves "volume" e "brilho" e imprima o valor, se ele existir, ou uma mensagem de “não definido”, se não existir. Dica: .get(key) em um HashMap devolve um Option, então você pode fazer match direto nele.
Ver solução
use std::collections::HashMap;
fn main() { let mut settings = HashMap::new(); settings.insert("volume", 80);
for key in ["volume", "brilho"] { match settings.get(key) { Some(value) => println!("{key} = {value}"), None => println!("{key} não está definido"), } }}Saída:
volume = 80brilho não está definidoOption<T>representa um valor que pode ou não estar presente. O Rust não temnull.Some(value)guarda alguma coisa;Nonequer dizer que não há nada ali.matché a forma segura de tratar os dois casos; o compilador não deixa você esquecer nenhum..unwrap_or(default)oferece um valor padrão;.unwrap()causa um panic se o valor forNone, então use com moderação.
Teste o que aprendeu
Seção intitulada “Teste o que aprendeu”Teste o que aprendeu
1. O que o Rust usa no lugar de null para um valor que pode estar ausente?
2. O que acontece quando você chama .unwrap() em um valor None?
3. O que age.unwrap_or(0) faz quando age é None?
4. Por que o match é considerado a forma segura de tirar um valor de um Option?
5. Quais métodos citados nesta página já devolvem um Option?
Pontuação: 0 / 5
Para saber mais
Seção intitulada “Para saber mais”- The Option Enum and Its Advantages Over Null Values — The Rust Book (em inglês): a explicação oficial de por que o Rust usa
Optionem vez denull. std::option— documentação da biblioteca padrão (em inglês): uma visão geral de tudo o que dá para fazer com umOption, incluindo métodos úteis comomap,and_theneis_some.- Option & unwrap — Rust by Example (em inglês): exemplos curtos que você pode rodar com
match,unwrape quando acontece um panic. Option<T>— documentação da std (em inglês): a lista completa de métodos, cada um com um pequeno exemplo que você pode copiar.