Option<T> em Rust: Some e None

Guia completo de Option<T> em Rust: pattern matching, unwrap, map, and_then, operador ?, conversão para Result e exemplos práticos.

O que é Option<T>

Option<T> é o tipo que Rust usa para representar valores que podem ou não existir. Ele tem exatamente duas variantes: Some(T) quando há um valor presente, e None quando não há. Diferente de linguagens que usam null ou nil, o Option força você a lidar explicitamente com a ausência de valor, eliminando a categoria inteira de erros de null pointer em tempo de compilação.

Use Option<T> sempre que uma função pode não retornar um resultado (busca em uma coleção, parsing opcional, primeiro elemento de uma lista vazia), ou quando um campo de uma struct é opcional. Praticamente todo código Rust usa Option intensivamente — ele está no prelude e pode ser usado sem importar nada.


Criando e Usando Option

fn main() {
    // Criando diretamente
    let algum_valor: Option<i32> = Some(42);
    let nenhum_valor: Option<i32> = None;

    // Retornado por métodos da stdlib
    let numeros = vec![10, 20, 30];
    let primeiro: Option<&i32> = numeros.first();     // Some(&10)
    let decimo: Option<&i32> = numeros.get(9);         // None

    let texto = "42abc";
    let resultado: Option<usize> = texto.find('a');    // Some(2)

    // Pattern matching — a forma mais completa
    match primeiro {
        Some(valor) => println!("Primeiro: {valor}"),
        None => println!("Lista vazia"),
    }

    // if let — quando só interessa o caso Some
    if let Some(pos) = resultado {
        println!("Encontrado na posição {pos}");
    }

    // let-else — quando None é o caso de erro (Rust 1.65+)
    let Some(val) = algum_valor else {
        println!("Sem valor!");
        return;
    };
    println!("Valor: {val}");

    println!("{nenhum_valor:?} {decimo:?}");
}

Tabela de Métodos Principais

MétodoDescriçãoExemplo
is_some()Retorna true se é SomeSome(1).is_some()true
is_none()Retorna true se é NoneNone::<i32>.is_none()true
unwrap()Extrai o valor ou panicSome(1).unwrap()1
unwrap_or(default)Extrai ou retorna defaultNone.unwrap_or(0)0
unwrap_or_else(f)Extrai ou calcula via closureNone.unwrap_or_else(|| calcular())
unwrap_or_default()Extrai ou usa Default::default()None::<i32>.unwrap_or_default()0
expect(msg)Como unwrap mas com mensagem de panicopt.expect("erro")
map(f)Transforma o valor internoSome(1).map(|x| x * 2)Some(2)
map_or(default, f)Transforma ou retorna defaultNone.map_or(0, |x| x * 2)0
and_then(f)Encadeia operações que retornam OptionSome(1).and_then(|x| Some(x + 1))
filter(pred)None se o predicado falharSome(4).filter(|x| x % 2 == 0)Some(4)
or(outra)Retorna self se Some, senão outraNone.or(Some(5))Some(5)
or_else(f)Como or, mas com closureNone.or_else(|| Some(5))
zip(outra)Combina dois Options em tuplaSome(1).zip(Some("a"))Some((1, "a"))
flatten()Achata Option<Option<T>>Some(Some(1)).flatten()Some(1)
ok_or(err)Converte para Result<T, E>Some(1).ok_or("erro")Ok(1)
ok_or_else(f)Converte para Result com closureNone.ok_or_else(|| "erro")
as_ref()Option<&T> a partir de &Option<T>opt.as_ref()
take()Pega o valor, deixando Noneopt.take()
replace(val)Substitui o valor, retornando o antigoopt.replace(5)

Exemplos Práticos

1. Encadeando Transformações com map e and_then

fn buscar_usuario(id: u64) -> Option<String> {
    match id {
        1 => Some("Ana Silva".to_string()),
        2 => Some("Bruno Costa".to_string()),
        _ => None,
    }
}

fn extrair_primeiro_nome(nome_completo: &str) -> Option<&str> {
    nome_completo.split_whitespace().next()
}

fn main() {
    // Encadeando operações que podem falhar
    let saudacao = buscar_usuario(1)
        .as_deref()  // Option<String> → Option<&str>
        .and_then(extrair_primeiro_nome)
        .map(|nome| format!("Olá, {nome}!"))
        .unwrap_or_else(|| "Usuário não encontrado".to_string());

    println!("{saudacao}"); // "Olá, Ana!"

    // Com usuário inexistente
    let saudacao2 = buscar_usuario(99)
        .as_deref()
        .and_then(extrair_primeiro_nome)
        .map(|nome| format!("Olá, {nome}!"))
        .unwrap_or_else(|| "Usuário não encontrado".to_string());

    println!("{saudacao2}"); // "Usuário não encontrado"
}

2. O Operador ? com Option

#[derive(Debug)]
struct Endereco {
    rua: String,
    numero: Option<u32>,
    complemento: Option<String>,
}

#[derive(Debug)]
struct Pessoa {
    nome: String,
    endereco: Option<Endereco>,
}

fn obter_complemento(pessoa: &Pessoa) -> Option<&str> {
    // O operador ? retorna None imediatamente se qualquer etapa for None
    pessoa
        .endereco
        .as_ref()?
        .complemento
        .as_deref()
}

fn main() {
    let pessoa1 = Pessoa {
        nome: "Ana".into(),
        endereco: Some(Endereco {
            rua: "Rua das Flores".into(),
            numero: Some(42),
            complemento: Some("Apto 301".into()),
        }),
    };

    let pessoa2 = Pessoa {
        nome: "Bruno".into(),
        endereco: None,
    };

    println!("{}: {:?}", pessoa1.nome, obter_complemento(&pessoa1)); // Some("Apto 301")
    println!("{}: {:?}", pessoa2.nome, obter_complemento(&pessoa2)); // None
}

3. Convertendo entre Option e Result

use std::num::ParseIntError;

fn buscar_config(chave: &str) -> Option<String> {
    match chave {
        "porta" => Some("8080".to_string()),
        "host" => Some("localhost".to_string()),
        _ => None,
    }
}

fn obter_porta() -> Result<u16, String> {
    buscar_config("porta")
        .ok_or_else(|| "Chave 'porta' não encontrada".to_string())?
        .parse::<u16>()
        .map_err(|e: ParseIntError| format!("Porta inválida: {e}"))
}

fn main() {
    match obter_porta() {
        Ok(porta) => println!("Servidor rodando na porta {porta}"),
        Err(erro) => eprintln!("Erro: {erro}"),
    }
}

4. Combinando Múltiplos Options com zip

fn calcular_imc(peso_kg: Option<f64>, altura_m: Option<f64>) -> Option<f64> {
    peso_kg
        .zip(altura_m)
        .filter(|(_, a)| *a > 0.0)
        .map(|(p, a)| p / (a * a))
}

fn classificar_imc(imc: f64) -> &'static str {
    match imc {
        x if x < 18.5 => "Abaixo do peso",
        x if x < 25.0 => "Peso normal",
        x if x < 30.0 => "Sobrepeso",
        _ => "Obesidade",
    }
}

fn main() {
    let resultado = calcular_imc(Some(70.0), Some(1.75))
        .map(|imc| format!("IMC: {imc:.1} - {}", classificar_imc(imc)))
        .unwrap_or_else(|| "Dados insuficientes".to_string());

    println!("{resultado}"); // "IMC: 22.9 - Peso normal"

    let incompleto = calcular_imc(Some(70.0), None);
    println!("{incompleto:?}"); // None
}

5. Coletando Options em um Vec

fn main() {
    let entradas = vec!["42", "abc", "7", "xyz", "100"];

    // collect() descarta os None automaticamente com flatten
    let numeros: Vec<i32> = entradas
        .iter()
        .map(|s| s.parse::<i32>().ok()) // Cada parse retorna Option<i32>
        .flatten()                        // Remove os None
        .collect();
    println!("Números: {numeros:?}"); // [42, 7, 100]

    // Alternativa com filter_map (equivalente a map + flatten)
    let numeros2: Vec<i32> = entradas
        .iter()
        .filter_map(|s| s.parse().ok())
        .collect();
    println!("Números: {numeros2:?}"); // [42, 7, 100]

    // collect() que falha no primeiro None
    let todos: Option<Vec<i32>> = vec!["1", "2", "3"]
        .iter()
        .map(|s| s.parse().ok())
        .collect();
    println!("Todos: {todos:?}"); // Some([1, 2, 3])

    let com_falha: Option<Vec<i32>> = vec!["1", "abc", "3"]
        .iter()
        .map(|s| s.parse().ok())
        .collect();
    println!("Com falha: {com_falha:?}"); // None
}

Dicas de Performance e Armadilhas

  1. Evite unwrap() em produção: Use unwrap() apenas em testes, protótipos ou quando tiver certeza absoluta de que o valor é Some. Prefira unwrap_or, unwrap_or_else, ou pattern matching.

  2. map vs and_then: Use map quando a transformação sempre retorna um valor. Use and_then quando a transformação também pode falhar (retorna Option). Usar map com uma função que retorna Option gera Option<Option<T>>.

  3. Custo zero: Option<T> não tem overhead de memória quando T é uma referência ou NonZero*. O compilador usa a representação nula para None, eliminando o byte extra.

  4. as_ref() para evitar movimentos: Quando tiver &Option<T> e precisar de Option<&T>, use as_ref(). Isso evita mover o valor para fora do Option.

  5. take() para extrair valores: option.take() substitui o valor por None e retorna o antigo. Útil quando precisa mover o valor de dentro de uma struct sem clonar.

  6. Option em iteradores: Iteradores possuem métodos como filter_map e find que trabalham naturalmente com Option, evitando verificações manuais.


Veja Também