Pular para o conteúdo

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.

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.

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

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"),
}
}
▶ Experimente você mesmo
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: Alice
Usuário não encontrado
▶ Experimente você mesmo

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

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

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 é Some

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: 8
Com valor padrão: 0
▶ Experimente você mesmo

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 = 80
brilho não está definido
▶ Experimente você mesmo
  • Option<T> representa um valor que pode ou não estar presente. O Rust não tem null.
  • Some(value) guarda alguma coisa; None quer 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 for None, então use com moderação.

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