Rust Admin

Como Usar Enums em Rust: Definição, Variantes e Dados Associados em Produção Já leu

Introdução: O Que São Enums em Rust? Enums (enumerações) são um dos pilares do sistema de tipos de Rust, permitindo definir um tipo que pode assumir múltiplos valores nomeados distintos. Diferentemente de linguagens como C ou Java, enums em Rust são muito mais poderosos: cada variante pode conter dados associados de qualquer tipo. Isso torna as enums ideais para representar estados, padrões discriminados e estruturas de dados heterogêneas com segurança de tipo garantida em tempo de compilação. Dominá-las é fundamental porque você as encontrará em praticamente todo código Rust profissional — desde e (enums built-in) até modelos de domínio complexos. Definição Básica e Variantes Simples Sintaxe Fundamental A definição de uma enum em Rust usa a palavra-chave seguida de um nome e um bloco contendo suas variantes: Neste exemplo simples, é uma enum com quatro variantes que não contêm dados adicionais. A forma mais comum de trabalhar com enums é usar para fazer correspondência de padrões — um mecanismo seguro

Introdução: O Que São Enums em Rust?

Enums (enumerações) são um dos pilares do sistema de tipos de Rust, permitindo definir um tipo que pode assumir múltiplos valores nomeados distintos. Diferentemente de linguagens como C ou Java, enums em Rust são muito mais poderosos: cada variante pode conter dados associados de qualquer tipo. Isso torna as enums ideais para representar estados, padrões discriminados e estruturas de dados heterogêneas com segurança de tipo garantida em tempo de compilação.

Dominá-las é fundamental porque você as encontrará em praticamente todo código Rust profissional — desde Option<T> e Result<T, E> (enums built-in) até modelos de domínio complexos.

Definição Básica e Variantes Simples

Sintaxe Fundamental

A definição de uma enum em Rust usa a palavra-chave enum seguida de um nome e um bloco contendo suas variantes:

enum Direcao {
    Norte,
    Sul,
    Leste,
    Oeste,
}

fn main() {
    let meu_caminho = Direcao::Norte;

    // As variantes são acessadas com a sintaxe NomeDaEnum::Variante
    match meu_caminho {
        Direcao::Norte => println!("Seguindo para o norte!"),
        Direcao::Sul => println!("Seguindo para o sul!"),
        Direcao::Leste => println!("Seguindo para o leste!"),
        Direcao::Oeste => println!("Seguindo para o oeste!"),
    }
}

Neste exemplo simples, Direcao é uma enum com quatro variantes que não contêm dados adicionais. A forma mais comum de trabalhar com enums é usar match para fazer correspondência de padrões — um mecanismo seguro e exaustivo que força você a tratar todas as variantes possíveis.

Variantes e Namespacing

Cada variante é automaticamente um construtor do tipo. Você pode importar uma variante específica para evitar repetir o prefixo Direcao:::

use Direcao::*;

let sentido = Leste; // Funciona sem prefixo após use

Dados Associados e Variantes Heterogêneas

Armazenando Dados nas Variantes

A verdadeira força das enums em Rust emerge quando você associa dados às variantes. Cada variante pode ter um tipo diferente e quantidade diferente de dados:

enum Resultado {
    Sucesso(String),           // Variante com String
    Erro(u32),                 // Variante com código de erro (u32)
    Pendente,                  // Sem dados associados
}

enum Mensagem {
    Texto(String),
    Audio { duracao: u32, formato: String },
    Video { largura: u32, altura: u32, fps: u32 },
}

fn processar_resultado(res: Resultado) {
    match res {
        Resultado::Sucesso(dados) => {
            println!("Operação bem-sucedida: {}", dados);
        }
        Resultado::Erro(codigo) => {
            println!("Erro com código: {}", codigo);
        }
        Resultado::Pendente => {
            println!("Ainda processando...");
        }
    }
}

fn main() {
    let msg1 = Mensagem::Texto("Olá, mundo!".to_string());
    let msg2 = Mensagem::Audio { duracao: 120, formato: "MP3".to_string() };

    match msg2 {
        Mensagem::Texto(conteudo) => println!("Texto: {}", conteudo),
        Mensagem::Audio { duracao, formato } => {
            println!("Áudio de {} segundos em {}", duracao, formato);
        }
        Mensagem::Video { largura, altura, fps } => {
            println!("Vídeo: {}x{} a {} fps", largura, altura, fps);
        }
    }
}

Observe que você pode usar dados nomeados (struct-like) ou dados posicionais (tuple-like) nas variantes. O compilador rastreia cada variante e seus tipos, prevenindo acessos indevidos em tempo de compilação.

Option e Result na Prática

As enums mais importantes em Rust são predefinidas na biblioteca padrão. Option<T> representa um valor que pode ou não existir, e Result<T, E> representa sucesso ou fracasso:

fn dividir(a: f64, b: f64) -> Result<f64, String> {
    if b == 0.0 {
        Err("Divisão por zero não permitida".to_string())
    } else {
        Ok(a / b)
    }
}

fn buscar_usuario(id: u32) -> Option<String> {
    if id > 0 && id <= 100 {
        Some(format!("Usuário {}", id))
    } else {
        None
    }
}

fn main() {
    match dividir(10.0, 2.0) {
        Ok(resultado) => println!("Resultado: {}", resultado),
        Err(msg) => println!("Erro: {}", msg),
    }

    match buscar_usuario(42) {
        Some(nome) => println!("Encontrado: {}", nome),
        None => println!("Usuário não encontrado"),
    }

    // Alternativa: métodos convenientes
    let resultado = dividir(15.0, 3.0).unwrap_or(0.0);
    println!("Com unwrap_or: {}", resultado);
}

Implementando Métodos em Enums

Assim como structs, enums podem ter métodos implementados via blocos impl:

enum Status {
    Ativo,
    Pausado,
    Encerrado,
}

impl Status {
    fn eh_ativo(&self) -> bool {
        matches!(self, Status::Ativo)
    }

    fn descricao(&self) -> &str {
        match self {
            Status::Ativo => "Sistema operacional normalmente",
            Status::Pausado => "Sistema em pausa temporária",
            Status::Encerrado => "Sistema desligado",
        }
    }
}

fn main() {
    let estado = Status::Ativo;
    println!("Status: {}", estado.descricao());
    println!("Está ativo? {}", estado.eh_ativo());
}

Este padrão é especialmente útil para adicionar comportamento e lógica de negócio diretamente às suas enums, mantendo o código organizado e orientado a objetos de forma idiomática em Rust.

Conclusão

Nesta aula, aprendemos que enums em Rust são tipos discriminados que podem armazenar dados heterogêneos de forma segura e que o compilador valida todas as possibilidades em tempo de compilação. O pattern matching via match é a forma idiomática de trabalhar com enums, forçando você a considerar todos os casos. Por fim, Option<T> e Result<T, E> são enums especiais usadas para tratamento de ausência de dados e erros, substituindo adequadamente valores nulos e exceções tradicionais.

Referências


Artigos relacionados