Enums (enumerações) são um recurso poderoso em Rust para definir tipos que podem ser um de vários valores possíveis. Diferente de linguagens como C ou Java, os enums em Rust não são apenas números; cada variante pode carregar dados de tipos diferentes, tornando-os extremamente versáteis. Nesta aula, exploraremos como definir enums, como associar dados a cada variante e como usar o famoso enum Option para lidar com valores opcionais de forma segura.

Definindo enums

Em Rust, um enum é definido com a palavra-chave enum seguida do nome e das variantes entre chaves. Cada variante é separada por vírgula. Por exemplo, podemos representar os estados de um semáforo:

enum TrafficLight {
    Red,
    Yellow,
    Green,
}

Para usar um valor do enum, usamos a sintaxe NomeDoEnum::Variante:

let light = TrafficLight::Red;

Enums podem ser combinados com match para executar código diferente para cada variante, garantindo que todos os casos sejam tratados. O compilador verifica exaustividade, ou seja, se você esqueceu de tratar alguma variante, o código não compila.

Variantes com dados

Cada variante de um enum pode carregar dados de tipos diferentes, como tuplas, structs ou tipos primitivos. Isso permite representar conceitos complexos de forma concisa. Por exemplo, um enum para mensagens de chat:

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

A variante Quit não tem dados; Move tem dois campos nomeados; Write tem uma String; ChangeColor tem três valores i32. Para criar uma instância:

let msg1 = Message::Write(String::from("Olá"));
let msg2 = Message::Move { x: 10, y: 20 };

No match, podemos extrair os dados de cada variante usando padrões:

match msg1 {
    Message::Quit => println!("Saindo"),
    Message::Move { x, y } => println!("Movendo para ({}, {})", x, y),
    Message::Write(text) => println!("Texto: {}", text),
    Message::ChangeColor(r, g, b) => println!("Cor: RGB({}, {}, {})", r, g, b),
}

Option como enum

Rust não tem null. Em vez disso, usa o enum Option<T> para representar um valor que pode existir ou não. Sua definição na biblioteca padrão é:

enum Option<T> {
    None,
    Some(T),
}

Isso força o programador a tratar ambos os casos, eliminando erros de ponteiro nulo. Exemplo:

fn dividir(a: f64, b: f64) -> Option<f64> {
    if b == 0.0 {
        None
    } else {
        Some(a / b)
    }
}

let resultado = dividir(10.0, 2.0);
match resultado {
    Some(valor) => println!("Resultado: {}", valor),
    None => println!("Erro: divisão por zero"),
}

O Option também oferece métodos úteis como unwrap, expect, map, and_then, etc., que permitem trabalhar com valores opcionais de forma funcional e segura.

Casos de uso

Enums são amplamente usados em Rust para modelar domínios com estados bem definidos. Exemplos comuns:

  • Máquinas de estado: Representar estados de um jogo (Menu, Jogando, Pausado, GameOver).
  • Tratamento de erros: Combinado com Result<T, E>, que também é um enum (Ok(T) e Err(E)).
  • Dados heterogêneos: Armazenar diferentes tipos em uma única lista, por exemplo, uma lista de tokens em um interpretador.
  • Configuração: Representar opções de configuração que podem ser de tipos diferentes.

Exemplo de enum para representar formas geométricas:

enum Shape {
    Circle(f64),
    Rectangle(f64, f64),
    Triangle(f64, f64, f64),
}

fn area(shape: &Shape) -> f64 {
    match shape {
        Shape::Circle(r) => std::f64::consts::PI * r * r,
        Shape::Rectangle(w, h) => w * h,
        Shape::Triangle(a, b, c) => {
            let s = (a + b + c) / 2.0;
            (s * (s - a) * (s - b) * (s - c)).sqrt()
        }
    }
}

Boas práticas

  • Use enums para representar estados mutuamente exclusivos com dados associados.
  • Prefira Option a valores sentinela como -1 ou null.
  • Sempre use match exaustivo para garantir que todos os casos sejam tratados.
  • Evite enums com muitas variantes; considere usar traits ou structs separadas se o número for grande.

Referências

Exercícios

  1. Defina um enum chamado Status com variantes Success (contendo uma String), Error (contendo um código i32 e uma mensagem) e Pending. Crie uma função que recebe um Status e imprime uma mensagem apropriada.
  2. ✓ Resposta:
    enum Status {
        Success(String),
        Error(i32, String),
        Pending,
    }
    
    fn process_status(status: Status) {
        match status {
            Status::Success(msg) => println!("Sucesso: {}", msg),
            Status::Error(code, msg) => println!("Erro {}: {}", code, msg),
            Status::Pending => println!("Aguardando..."),
        }
    }
  3. Escreva uma função que recebe um Option<String> e retorna o comprimento da string se existir, ou 0 se for None.
  4. ✓ Resposta:
    fn length_or_zero(s: Option<String>) -> usize {
        match s {
            Some(text) => text.len(),
            None => 0,
        }
    }
  5. Crie um enum Temperature com variantes Celsius(f64) e Fahrenheit(f64). Implemente um método to_celsius que converte para Celsius.
  6. ✓ Resposta:
    enum Temperature {
        Celsius(f64),
        Fahrenheit(f64),
    }
    
    impl Temperature {
        fn to_celsius(&self) -> f64 {
            match self {
                Temperature::Celsius(c) => *c,
                Temperature::Fahrenheit(f) => (f - 32.0) * 5.0 / 9.0,
            }
        }
    }
  7. Use o enum Option para implementar uma função safe_divide que retorna Option<f64>. Se o denominador for zero, retorne None.
  8. ✓ Resposta:
    fn safe_divide(a: f64, b: f64) -> Option<f64> {
        if b == 0.0 {
            None
        } else {
            Some(a / b)
        }
    }
  9. Defina um enum WebEvent com variantes PageLoad, KeyPress(char), Click { x: i64, y: i64 }. Crie uma função que recebe um vetor de eventos e imprime uma descrição de cada um.
  10. ✓ Resposta:
    enum WebEvent {
        PageLoad,
        KeyPress(char),
        Click { x: i64, y: i64 },
    }
    
    fn handle_events(events: Vec<WebEvent>) {
        for event in events {
            match event {
                WebEvent::PageLoad => println!("Página carregada"),
                WebEvent::KeyPress(c) => println!("Tecla pressionada: {}", c),
                WebEvent::Click { x, y } => println!("Clique em ({}, {})", x, y),
            }
        }
    }