Result em Rust
Se o Option representa um valor que pode estar ausente, o Result representa uma operação que pode falhar. E, ao contrário de um valor ausente, uma falha costuma vir acompanhada de um motivo.
Como é o Result
Seção intitulada “Como é o Result”Result é mais um enum da biblioteca padrão, desta vez com duas variantes que carregam dados:
enum Result<T, E> { Ok(T), // sucesso, com um valor Err(E), // falha, com um erro}Uma função que pode falhar
Seção intitulada “Uma função que pode falhar”O .parse(), que você conheceu na página de Conversão de tipos, é um bom exemplo real: transformar texto em número pode falhar se o texto não for, de fato, um número:
fn main() { let good: Result<i32, std::num::ParseIntError> = "42".parse(); let bad: Result<i32, std::num::ParseIntError> = "abc".parse();
println!("{:?}", good); println!("{:?}", bad);}Saída:
Ok(42)Err(ParseIntError { kind: InvalidDigit })O InvalidDigit (dígito inválido) vem do próprio Rust: ele está dizendo que encontrou um caractere que não é um dígito.
Tratando um Result com match
Seção intitulada “Tratando um Result com match”fn main() { let input = "42";
match input.parse::<i32>() { Ok(number) => println!("Convertido: {number}"), Err(e) => println!("Falha na conversão: {e}"), }}É o mesmo formato de tratar um Option com match. A diferença é que o braço Err entrega um valor de erro de verdade, que você pode examinar ou mostrar, e não só um “não tinha nada aqui”.
Escrevendo sua própria função que devolve Result
Seção intitulada “Escrevendo sua própria função que devolve Result”fn divide(a: f64, b: f64) -> Result<f64, String> { if b == 0.0 { Err(String::from("não é possível dividir por zero")) } else { Ok(a / b) }}
fn main() { match divide(10.0, 2.0) { Ok(result) => println!("Resultado: {result}"), Err(e) => println!("Erro: {e}"), }
match divide(10.0, 0.0) { Ok(result) => println!("Resultado: {result}"), Err(e) => println!("Erro: {e}"), }}Saída:
Resultado: 5Erro: não é possível dividir por zeroDevolver Result<f64, String> obriga quem chama a função a lidar com a possibilidade de falha. Não tem como tratar uma divisão por zero como se fosse um número normal sem perceber.
O operador ?
Seção intitulada “O operador ?”Encadear várias operações que podem falhar leva a blocos match aninhados e difíceis de ler. O operador ? resolve isso. Ele quer dizer: “se isto for Err, devolva esse erro imediatamente da função atual; se não for, me dê o valor que está dentro do Ok”.
fn parse_and_double(input: &str) -> Result<i32, std::num::ParseIntError> { let number = input.parse::<i32>()?; // retorna mais cedo se a conversão falhar Ok(number * 2)}
fn main() { println!("{:?}", parse_and_double("21")); println!("{:?}", parse_and_double("oops"));}Saída:
Ok(42)Err(ParseIntError { kind: InvalidDigit })Sem o ?, essa linha precisaria de um match só para tirar o number de dentro ou sair mais cedo com o erro. O ? faz exatamente isso com um único caractere. Ele só funciona dentro de uma função que também devolve um Result (ou um Option), porque é isso que dá a ele um lugar para onde devolver o erro.
Result ou panic!?
Seção intitulada “Result ou panic!?”O Rust também tem o panic!, que para o programa na hora. Ele é explicado com mais detalhes na próxima página. Como regra geral: use Result para falhas que quem chama talvez queira tratar (uma entrada inválida, um arquivo que não existe); deixe o panic! para situações que indicam um bug de verdade, em que continuar seria pior do que parar.
Quando as suas funções passam a falhar de vários jeitos diferentes, um erro do tipo String simples deixa de ser suficiente. A página de Erros personalizados mostra como definir o seu próprio tipo de erro, e a de anyhow e thiserror mostra os crates que a maioria dos projetos Rust usa para reduzir o código repetitivo.
Prática
Seção intitulada “Prática”1. Verificando uma idade
Seção intitulada “1. Verificando uma idade”Escreva fn parse_age(input: &str) -> Result<u32, String>. Ela deve devolver Err com uma mensagem útil se o texto não for um número ou se o número passar de 150. Teste com "34", "abc" e "200". Dica: faça match em input.parse::<u32>() e, dentro do braço Ok, use um if para conferir o tamanho.
Ver solução
fn parse_age(input: &str) -> Result<u32, String> { match input.parse::<u32>() { Ok(age) => { if age > 150 { Err(format!("{age} é velho demais para ser real")) } else { Ok(age) } } Err(_) => Err(format!("'{input}' não é um número")), }}
fn main() { for input in ["34", "abc", "200"] { match parse_age(input) { Ok(age) => println!("Idade: {age}"), Err(e) => println!("Erro: {e}"), } }}Saída:
Idade: 34Erro: 'abc' não é um númeroErro: 200 é velho demais para ser real2. Somando dois números com ?
Seção intitulada “2. Somando dois números com ?”Escreva fn add_strings(a: &str, b: &str) -> Result<i32, std::num::ParseIntError>, que converte as duas strings em números e devolve a soma. Use ? em vez de match. Imprima o resultado para ("10", "20") e ("10", "vinte"). Dica: você precisa de um ? para cada parse, e a última linha embrulha a soma em Ok(...).
Ver solução
fn add_strings(a: &str, b: &str) -> Result<i32, std::num::ParseIntError> { let x = a.parse::<i32>()?; let y = b.parse::<i32>()?; Ok(x + y)}
fn main() { println!("{:?}", add_strings("10", "20")); println!("{:?}", add_strings("10", "vinte"));}Saída:
Ok(30)Err(ParseIntError { kind: InvalidDigit })Err(ParseIntError { kind: InvalidDigit }) é como o Rust mostra o erro com {:?}: "vinte" contém um dígito inválido (InvalidDigit), e o ? devolveu esse erro na hora, sem chegar a fazer a soma.
Result<T, E>representa uma operação que pode dar certo (Ok) ou falhar (Err), com um valor de erro.matchtrata os dois casos de forma explícita, com o mesmo padrão usado noOption.- O operador
?devolve umErrmais cedo de forma automática, então você não precisa de ummatchdepois de cada chamada que pode falhar. - Prefira
Resultapanic!para erros dos quais quem chama pode se recuperar.
Teste o que aprendeu
Seção intitulada “Teste o que aprendeu”Teste o que aprendeu
1. Quais são as duas variantes de Result<T, E>?
2. O que o operador ? faz dentro de uma função que devolve um Result?
3. Segundo esta página, quando você deve preferir Result a panic!?
4. No exemplo de divide desta página, o que divide(10.0, 0.0) devolve?
5. Por que o operador ? só funciona dentro de uma função que também devolve um Result (ou Option)?
Pontuação: 0 / 5
Para saber mais
Seção intitulada “Para saber mais”- Recoverable Errors with Result — The Rust Book (em inglês): o capítulo oficial sobre
Result,unwrap,expecte o operador?, com exemplos de abertura de arquivos. std::result— documentação da biblioteca padrão (em inglês): uma visão geral de como trabalhar comResult, incluindo métodos comomap_err,unwrap_oreok.- Result — Rust by Example (em inglês): exemplos que você pode rodar de como tratar um
Resulte devolvê-lo domain. - The ? operator — Rust by Example (em inglês): um olhar curto e direto sobre como o
?substitui blocosmatchaninhados.