Rust: Enums e Option no Rust
Última atualização: 2026-08-26
As enums são um dos tipos mais poderosos do Rust — elas permitem que um valor seja uma das variantes possíveis de um conjunto, e cada variante pode conter dados diferentes.
Se uma estrutura é um tipo “e” (possui todos os campos ao mesmo tempo), então uma enumeração é um tipo “ou” (é uma coisa ou outra).
1. O que você vai aprender
- Definição de tipos de enumeração e variantes
- As variantes de enumeração podem conter diferentes tipos de dados
- Use
implpara adicionar métodos a uma enumeração Option<T>Enumeração — A abordagem do Rust em relação aos “valores nulos”- Verificação da exaustividade de enumerações e instruções
match Option<T>métodos comuns (unwrap, map, expect)
2. Diagramas conceituais
flowchart LR
subgraph "enum Option<T>"
S["Some(T)<br>Not null"]
N["None<br>No value"]
end
subgraph "Use Cases"
V["Calculation Results"] -->|"Success"| S
F["Calculation Results"] -->|"Failure/None"| N
end
S --> MATCH["match Processing"]
N --> MATCH
MATCH -->|"Some(v) => Usage Value v"| OK["✅ Safety"]
MATCH -->|"None => Handling Null Values"| SAFE["✅ No crashes"]
3. A história de um sistema de pedidos
(1) Desafio: Representar o status do pedido por meio de números
Tom está desenvolvendo um sistema de comércio eletrônico. Os pedidos têm quatro status:
0 = Payable, 1 = Paid, 2 = Shipped, 3 = Completed
let order_status = 0; // Payable
// But what if someone were to write order_status = 99?
// Or confuse the status with the quantity: let order_status = product_count;
Representar estados por meio de números inteiros é uma prática comum em C — mas não é segura em termos de tipos. Qualquer número inteiro pode ser atribuído a um “estado”, e o compilador não fará essa verificação para você. Além disso, não há relação entre os estados, o que torna essa abordagem propensa a erros.
(2) Soluções para enumerações em Rust
enum OrderStatus {
Pending, // Payable
Paid, // Paid
Shipped, // Shipped -- you can bring your shipping tracking number
Delivered, // Completed -- delivery time (in-person)
}
fn main() {
let status = OrderStatus::Paid;
match status {
OrderStatus::Pending => println!("Please complete the payment"),
OrderStatus::Paid => println!("Paid, awaiting shipment"),
OrderStatus::Shipped => println!("Shipped, on the way"),
OrderStatus::Delivered => println!("Delivered, thank you for your purchase"),
}
// The compiler ensures that: you won't forget to handle any state!
// If a new status is added Cancelled, forgot to update match, the compiler will report an error
}
Uma enum agrupa todas as “opções” possíveis em um único tipo, e o uso de
matchcom uma enum garante uma verificação exaustiva — todos os estados são tratados, sem que nenhum seja omitido.
4. Tipos de enumeração
(1) As variantes de enumeração podem conter dados
enum Message {
Quit, // No data available
Move { x: i32, y: i32 }, // Anonymous Structures
Write(String), // Single value
ChangeColor(i32, i32, i32), // Tuple
}
| Variante de enumeração | Dados transportados | Utilização |
|---|---|---|
Quit |
Nenhum | Apenas um marcador |
Move { x, y } |
Estrutura anônima | Message::Move { x: 10, y: 20 } |
Write(String) |
Estrutura de tupla | Message::Write("hello".to_string()) |
ChangeColor(i,i,i) |
Estrutura de tupla | Message::ChangeColor(255, 0, 0) |
(2) Referência rápida aos métodos de opções comuns
| Método | Tipo de retorno | Descrição | Comportamento quando None |
|---|---|---|---|
unwrap() |
T |
Extrair valor | pânico |
expect(msg) |
T |
Recuperar valor (mensagem personalizada) | panic + msg |
unwrap_or(default) |
T |
Recuperar valor ou valor padrão | Retornar valor padrão |
map(f) |
Option<U> |
Converter o valor em “Some” | Manter “None” |
and_then(f) |
Option<U> |
Operação da opção de cadeia | Não manter |
filter(f) |
Option<T> |
Filtro de condição | Nenhum, se não for atendido |
is_some() |
bool |
Possui um valor | false |
is_none() |
bool |
É Nenhuma | verdadeiro |
ok_or(err) |
Result<T, E> |
Converter para o resultado | Err(err) |
(3) Comparação entre a opção e a hipótese nula
| Dimensão | Option<T> |
null (Outros idiomas) |
|---|---|---|
| Segurança de tipos | O compilador impõe o tratamento de valores nulos | Qualquer referência pode ser nula |
| Verificações de nulo | Substituição automática por meio de match/encadeamento de métodos |
Requer verificações manuais if |
| Esqueci de verificar | Erro de compilação | Exceção NullPointerException em tempo de execução |
| Operações encadeadas | map/and_then, etc. | Requer instruções if aninhadas ou encadeamento opcional |
(4) Layout de memória das enums
Memória utilizada por uma enumeração = memória utilizada pela variante maior + 1 byte para a tag. O compilador usa a tag para determinar qual variante está armazenada no momento.
graph TB
subgraph "Message Enumerations in Memory"
TAG[tag: 1 Byte] -->|Indicates which variant is currently selected| LABEL
DATA[Data on the Largest Variant: 8 Byte] --> LABEL[In total 9 Byte]
end
5. Exemplos de enumeração
▶ Exemplo 1: As variantes de uma enumeração contêm diferentes tipos de dados (Dificuldade ⭐⭐)
// ============================================
// Enumeration variants carry data -- Message Type
// ============================================
#[derive(Debug)]
enum Message {
Quit, // No data available
Move { x: i32, y: i32 }, // Anonymous Structures
Write(String), // Tuple Structure
ChangeColor(i32, i32, i32), // Tuple Structure
}
impl Message {
fn call(&self) {
match self {
Message::Quit => println!("Quit: Exit"),
Message::Move { x, y } => println!("Move: Move to ({}, {})", x, y),
Message::Write(text) => println!("Write: Message Content: {}", text),
Message::ChangeColor(r, g, b) => {
println!("ChangeColor: Color RGB({}, {}, {})", r, g, b);
}
}
}
}
fn main() {
let messages = vec![
Message::Write(String::from("Hello")),
Message::Move { x: 10, y: 20 },
Message::ChangeColor(255, 0, 0),
Message::Quit,
];
for msg in &messages {
msg.call();
}
}
Resultado:
Write: Message Content: Hello
Move: Move to (10, 20)
ChangeColor: Color RGB(255, 0, 0)
Quit: Exit
Cada variante de uma enumeração pode conter um número e um tipo diferentes de dados.
Quitnão contém dados,Movecontém uma estrutura anônima,Writecontém uma String eChangeColorcontém três i32s. É nesse ponto que as enumerações se mostram mais flexíveis do que as estruturas.
▶ Exemplo 2: Option<T> — Como lidar com valores nulos em Rust (Dificuldade ⭐⭐)
// ============================================
// Option<T> Enumeration: Not null (Some) or null (None)
// ============================================
// Option Definition (in the standard library)
// enum Option<T> {
// Some(T),
// None,
// }
fn divide(a: f64, b: f64) -> Option<f64> {
if b == 0.0 {
None // Back "no value" -- instead of crashing or returning NaN
} else {
Some(a / b) // Back "not null"
}
}
fn main() {
let result1 = divide(10.0, 2.0);
let result2 = divide(10.0, 0.0);
// Use match to process Option
match result1 {
Some(value) => println!("10 / 2 = {}", value),
None => println!("The divisor is 0"),
}
match result2 {
Some(value) => println!("10 / 0 = {}", value),
None => println!("The divisor is 0"),
}
// Simplify: unwrap or expect (risky, but convenient)
// println!("{}", result2.unwrap()); // ❌ unwrap on None will panic!
println!("result1 the value of: {}", result1.unwrap()); // ✅ unwrap on Some is safe
}
Resultado:
10 / 2 = 5
The divisor is 0
result1 the value of: 5
Option<T>é a forma padrão do Rust de lidar com valores “que podem ser nulos”. Ao contrário de outras linguagens, que causam uma falha em tempo de execução comnull/nil/None, oOption<T>do Rust obriga você a lidar explicitamente tanto com os casos “não nulos” quanto com os “nulos”, usandomatchouunwrap.
▶ Exemplo 3: Métodos comuns de Option (Dificuldade ⭐⭐)
// ============================================
// Option Practical Methods for: map, unwrap_or, expect
// ============================================
fn main() {
let some_value: Option<i32> = Some(10);
let none_value: Option<i32> = None;
// map: If there is a value, convert it; no value, maintain None
let doubled_some = some_value.map(|x| x * 2);
let doubled_none = none_value.map(|x| x * 2);
println!("map after: {:?}, {:?}", doubled_some, doubled_none);
// unwrap_or: Returns a value, if no value is provided, the default is used.
println!("unwrap_or: {}, {}",
some_value.unwrap_or(0), // 10
none_value.unwrap_or(0), // 0
);
// expect: Returns a value, if no value, panic and display a custom message
println!("expect: {}", some_value.expect("There should be a value"));
// println!("expect: {}", none_value.expect("OH NO! No value!")); // ❌ panic
// is_some / is_none: Check if a value exists
println!("is_some: {}, is_none: {}",
some_value.is_some(),
none_value.is_none(),
);
// Example of a Chain Call
let result = Some(5)
.map(|x| x + 3)
.map(|x| x * 2)
.unwrap_or(0);
println!("Results of a chained call: {}", result); // 16
}
Resultado:
map after: Some(20), None
unwrap_or: 10, 0
expect: 10
is_some: true, is_none: true
Results of a chained call: 16
Option<T>oferece um conjunto de métodos que permitem encadear operações em valores potencialmente nulos em um estilo funcional, sem precisar escrevermatchtodas as vezes.mapconverte valores,unwrap_orfornece valores padrão eexpectoferece mensagens de erro mais claras durante a depuração.
▶ Exemplo 4: Exaustividade das enumerações e match (Dificuldade ⭐⭐⭐)
// ============================================
// Enumerate proprietary match Exhaustive Check Demonstration
// ============================================
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => {
println!("Lucky Penny!");
1
}
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
// If I don't write Quarter, the compiler will report an error: non-exhaustive patterns
}
}
// Enumeration with Data
#[derive(Debug)]
enum IpAddr {
V4(u8, u8, u8, u8), // 4 bytes
V6(String), // Complete IPv6 Address
}
fn main() {
let coin = Coin::Quarter;
println!("Value of Coins: {} cents", value_in_cents(coin));
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));
match home {
IpAddr::V4(a, b, c, d) => {
println!("IPv4: {}.{}.{}.{}", a, b, c, d);
}
IpAddr::V6(addr) => {
println!("IPv6: {}", addr);
}
}
}
Resultado:
Value of Coins: 25 cents
IPv4: 127.0.0.1
Todas as variantes de uma enumeração devem ser tratadas em uma instrução
match— isso é conhecido como “verificação exaustiva”. Se você se esquecer de tratar uma variante, o compilador reportará um erro. Isso é muito mais seguro do que oswitchdo C (no C, se você se esquecer de umcaseem uma instruçãoswitch, ele simplesmente a ignorará sem avisar).
▶ Exemplo 5: Exercício abrangente — Analisador de entradas do usuário (Dificuldade ⭐⭐⭐)
// ============================================
// Comprehensive Example: Enumeration + Option + match Real-World Experience
// ============================================
#[derive(Debug)]
enum Command {
Help,
Greet(String),
Calc(f64, char, f64),
Quit,
}
fn parse_command(input: &str) -> Option<Command> {
let parts: Vec<&str> = input.trim().split_whitespace().collect();
if parts.is_empty() {
return None;
}
match parts[0] {
"help" | "h" => Some(Command::Help),
"quit" | "q" => Some(Command::Quit),
"greet" if parts.len() > 1 => {
Some(Command::Greet(parts[1..].join(" ")))
}
"calc" if parts.len() == 4 => {
let a = parts[1].parse::<f64>().ok()?;
let op = parts[2].chars().next()?;
let b = parts[3].parse::<f64>().ok()?;
Some(Command::Calc(a, op, b))
}
_ => None,
}
}
fn execute(cmd: Command) -> bool {
match cmd {
Command::Help => {
println!("Command: help | greet <name> | calc <a> <op> <b> | quit");
true
}
Command::Greet(name) => {
println!("Hello, {}!", name);
true
}
Command::Calc(a, op, b) => {
let result = match op {
'+' => Some(a + b),
'-' => Some(a - b),
'*' => Some(a * b),
'/' if b != 0.0 => Some(a / b),
'/' => { println!("Error: Divisor is zero"); None }
_ => { println!("Unknown Operator: {}", op); None }
};
result.map(|r| println!("{} {} {} = {:.2}", a, op, b, r));
true
}
Command::Quit => {
println!("Goodbye!");
false
}
}
}
fn main() {
let inputs = [
"help",
"greet Alice",
"calc 10 + 5",
"calc 20 / 4",
"calc 1 / 0",
"unknown",
"quit",
];
println!("=== Command Parser ===");
for input in inputs {
println!("\n> {}", input);
match parse_command(input) {
Some(cmd) => {
if !execute(cmd) { break; }
}
None => println!("Unrecognized command"),
}
}
}
Resultado:
=== Command Parser ===
> help
Command: help | greet <name> | calc <a> <op> <b> | quit
> greet Alice
Hello, Alice!
> calc 10 + 5
10 + 5 = 15.00
> calc 20 / 4
20 / 4 = 5.00
> calc 1 / 0
Error: Divisor is zero
> unknown
Unrecognized command
> quit
Goodbye!
Este exemplo combina o uso de variantes de enumeração que contêm dados diferentes,
Optionoperações encadeadas (ok()?,.ok()?),matchverificações exaustivas e o padrãoparse_commandreturnOption<Command>. Enum + Option + match é o paradigma padrão em Rust para lidar com “múltiplos resultados possíveis”.
❓ Perguntas Frequentes
P: Qual é a diferença entre uma enumeração e uma estrutura? R: Uma estrutura é do tipo “ambos” (tipo “e”), enquanto uma enumeração é do tipo “um ou outro” (tipo “ou”).
struct Car { color: String, model: String }representa um carro que possui tanto uma cor quanto um modelo.enum Car { Electric, Gas, Hybrid }representa um carro que é um carro elétrico, um carro a gasolina ou um híbrido.
P: Por que o Rust usa
Optionem vez denull? R:Optioné seguro em termos de tipo — o compilador obriga você a lidar com casos em que um valor “pode ser nulo”. O problema comnullé que qualquer referência pode sernull, mas o compilador não solicita que você verifique isso. Tony Hoare (o inventor denull) chamou isso de “o bug de um bilhão de dólares”.
P: Qual é a diferença entre
someeunwrap? R:somesignifica “pode haver um valor”, enquantounwrapsignifica “tenho certeza de que há um valor; caso contrário, o programa irá travar”.unwrapé prático durante a fase de protótipo, mas no código de produção, você deve usar métodos mais seguros, comomatchouunwrap_or.
P: Quantos dados uma enumeração pode armazenar? R: O espaço ocupado por uma enumeração = o tamanho da variante maior + uma tag de 1 byte. Por exemplo,
Option<u8>tem 2 bytes (1 byte u8 + 1 byte de tag), enquantoOption<&str>e&strtêm o mesmo tamanho (o compilador otimiza o espaço da tag).
P: Como se nomeia os dados nas variantes de enumeração? R: Use a sintaxe de estrutura anônima.
Message::Move { x: i32, y: i32 }Defina-a dessa forma, crie-a comMessage::Move { x: 10, y: 20 }e desmonte-a comMessage::Move { x, y }durante ummatch. É mais claro do que usar estruturas de tupla.
📖 Resumo
- Enum é um “tipo de união” — um valor pode ser uma das várias variantes
- As variantes de enumeração podem conter dados (na forma de tuplas ou estruturas anônimas)
Option<T>é a enumeração mais utilizada na biblioteca padrão, servindo como substituto para o valor nulo- Quando usado com
match, o compilador impõe uma verificação exaustivaOptionoferece métodos encadeados, comomap,unwrap_oreexpect. - Uso de memória de uma enumeração ≈ tamanho máximo da variante + 1 byte de tag
📝 Exercícios
- Dificuldade ⭐: Defina uma enumeração
Weekdayque contenha de segunda a domingo. Escreva uma função que recebaWeekdaycomo entrada e retorne se o dia em questão é um dia da semana ou um fim de semana (usandomatch). - Dificuldade ⭐⭐: Escreva uma função
fn safe_sqrt(x: f64) -> Option<f64>que retorneSome(x.sqrt())se x >= 0 eNonecaso contrário. Teste tanto números positivos quanto negativos emmain. - Dificuldade ⭐⭐⭐: Defina uma enumeração
Temperatureque inclua duas variantes:Celsius(f64)eFahrenheit(f64). Implemente os métodosfn to_celsius(&self) -> f64efn to_fahrenheit(&self) -> f64. Emmain, crie dois valores de temperatura e faça a conversão entre eles.