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


2. Diagramas conceituais

100%
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:

TEXT 📖 Somente leitura
0 = Payable, 1 = Paid, 2 = Shipped, 3 = Completed
RUST
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

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 match com 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

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

100%
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 ⭐⭐)

RUST
// ============================================
// 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:

TEXT 📖 Somente leitura
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. Quit não contém dados, Move contém uma estrutura anônima, Write contém uma String e ChangeColor conté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 ⭐⭐)

RUST
// ============================================
// 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:

TEXT 📖 Somente leitura
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 com null/nil/None, o Option<T> do Rust obriga você a lidar explicitamente tanto com os casos “não nulos” quanto com os “nulos”, usando match ou unwrap.


▶ Exemplo 3: Métodos comuns de Option (Dificuldade ⭐⭐)

RUST
// ============================================
// 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:

TEXT 📖 Somente leitura
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 escrever match todas as vezes. map converte valores, unwrap_or fornece valores padrão e expect oferece mensagens de erro mais claras durante a depuração.


▶ Exemplo 4: Exaustividade das enumerações e match (Dificuldade ⭐⭐⭐)

RUST
// ============================================
// 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:

TEXT 📖 Somente leitura
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 o switch do C (no C, se você se esquecer de um case em uma instrução switch, ele simplesmente a ignorará sem avisar).


▶ Exemplo 5: Exercício abrangente — Analisador de entradas do usuário (Dificuldade ⭐⭐⭐)

RUST
// ============================================
// 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:

TEXT 📖 Somente leitura
=== 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, Option operações encadeadas (ok()?, .ok()?), match verificações exaustivas e o padrão parse_command return Option<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 Option em vez de null? R: Option é seguro em termos de tipo — o compilador obriga você a lidar com casos em que um valor “pode ser nulo”. O problema com null é que qualquer referência pode ser null, mas o compilador não solicita que você verifique isso. Tony Hoare (o inventor de null) chamou isso de “o bug de um bilhão de dólares”.

P: Qual é a diferença entre some e unwrap? R: some significa “pode haver um valor”, enquanto unwrap significa “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, como match ou unwrap_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), enquanto Option<&str> e &str tê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 com Message::Move { x: 10, y: 20 } e desmonte-a com Message::Move { x, y } durante um match. É mais claro do que usar estruturas de tupla.


📖 Resumo


📝 Exercícios

  1. Dificuldade ⭐: Defina uma enumeração Weekday que contenha de segunda a domingo. Escreva uma função que receba Weekday como entrada e retorne se o dia em questão é um dia da semana ou um fim de semana (usando match).
  2. Dificuldade ⭐⭐: Escreva uma função fn safe_sqrt(x: f64) -> Option<f64> que retorne Some(x.sqrt()) se x >= 0 e None caso contrário. Teste tanto números positivos quanto negativos em main.
  3. Dificuldade ⭐⭐⭐: Defina uma enumeração Temperature que inclua duas variantes: Celsius(f64) e Fahrenheit(f64). Implemente os métodos fn to_celsius(&self) -> f64 e fn to_fahrenheit(&self) -> f64. Em main, crie dois valores de temperatura e faça a conversão entre eles.
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%