De Option para Result em Rust: tutorial sem macros

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Por que você está cansado de escrever macros para Option em Result?

Você programa em Rust e, de repente, percebe: você tem um Option<T>, mas precisa de um Result<T, E>. Parece um detalhe. Mas se você é iniciante, o primeiro pensamento é: "Provavelmente preciso de uma macro!" Ou você já é um desenvolvedor experiente, mas toda vez escreve a mesma coisa: match option { Some(v) => Ok(v), None => Err("error") }. Concordo, isso é cansativo.

A pergunta no Stack Overflow "Existe uma maneira de simplificar a conversão de um Option em um Result sem uma macro?" acumulou milhares de visualizações. Não é por acaso: o problema é familiar para todos que trabalham com Rust. Mas a boa notícia é que a solução existe e é surpreendentemente simples. Neste tutorial, abordaremos todas as formas, desde as básicas até as elegantes, sem uma única macro.

Não vamos apenas dar a resposta — vamos mostrar como pensar como um desenvolvedor Rust. Vamos lá!



O que são Option e Result em Rust?

Antes de converter, vamos garantir que estamos na mesma página.

  • Option<T> — é um tipo que pode ser Some(T) (valor existe) ou None (valor não existe). É usado quando a ausência de valor é uma situação normal (por exemplo, buscar um elemento em uma coleção).
  • Result<T, E> — é um tipo que pode ser Ok(T) (tudo certo) ou Err(E) (ocorreu um erro). É usado para operações que podem falhar (por exemplo, ler um arquivo).

Problema: às vezes você tem um Option, mas pela lógica do programa, a ausência de valor é um erro. É aí que a conversão é necessária.



Forma básica: match (mas você já sabe disso)

A forma mais óbvia e frequentemente usada por iniciantes é o match. Sim, funciona, mas é verboso:

fn get_user_name(id: u32) -> Option<String> {    // ... alguma lógica    if id == 1 { Some("Alice".to_string()) } else { None }}

fn main() { let user_id = 2; let name = match get_user_name(user_id) { Some(name) => Ok(name), None => Err("User not found"), }; println!("{:?}", name); // Err("User not found")}

Este código é correto, mas se houver dezenas dessas conversões no projeto, você se cansará de escrever match. Além disso, é fácil errar e esquecer de tratar o None. Precisamos de algo mais conciso.



Método ok_or: seu novo melhor amigo

A biblioteca padrão do Rust fornece o método ok_or para Option. Ele faz exatamente o que precisamos: converte Some(v) em Ok(v), e None em Err(error), onde error é o valor que você passa.

fn main() {    let user_id = 2;    let name = get_user_name(user_id).ok_or("User not found");    println!("{:?}", name); // Err("User not found")}

Apenas uma linha! Mas há um detalhe: ok_or recebe um valor por posse (owned value). Se você passar um literal de string, como no exemplo, funciona, porque &str implementa Into<String>. Mas se você precisar passar um erro que já está armazenado em algum lugar, isso pode causar cópias desnecessárias.

Para esses casos, existe ok_or_else — ele recebe um closure que é chamado apenas se o Option for None. Isso economiza recursos:

fn main() {    let user_id = 2;    let error_msg = format!("User with id {} not found", user_id);    let name = get_user_name(user_id).ok_or_else(|| error_msg.clone());    // ou ainda melhor:    let name = get_user_name(user_id).ok_or_else(|| format!("User {} not found", user_id));    println!("{:?}", name);}

Dica: use ok_or se a mensagem de erro for uma constante. Use ok_or_else se a mensagem de erro precisar ser calculada ou se você quiser evitar cópias desnecessárias.

Blogs

Book Recommendations