O que é Result<T,E>
Result<T, E> é o tipo que Rust usa para representar operações que podem falhar de forma recuperável. Ele tem duas variantes: Ok(T) para o caso de sucesso e Err(E) para o caso de erro. Diferente de exceções em outras linguagens, o Result torna os erros explícitos no tipo de retorno — o compilador obriga você a lidar com eles.
Use Result sempre que uma operação pode falhar por motivos previsíveis: leitura de arquivos, parsing de dados, requisições de rede, validação de entrada do usuário. Para erros que indicam bugs no programa (índice fora dos limites, estado impossível), Rust usa panic!. Para valores opcionais sem erro, use Option<T>.
Criando e Usando Result
use std::num::ParseIntError;
fn dividir(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err("Divisão por zero".to_string())
} else {
Ok(a / b)
}
}
fn main() {
// Pattern matching — forma mais completa
match dividir(10.0, 3.0) {
Ok(resultado) => println!("Resultado: {resultado:.2}"),
Err(erro) => println!("Erro: {erro}"),
}
// if let — quando só interessa um caso
if let Err(e) = dividir(10.0, 0.0) {
eprintln!("Erro: {e}");
}
// let-else para early return
let Ok(valor) = dividir(10.0, 2.0) else {
eprintln!("Falhou!");
return;
};
println!("Valor: {valor}");
// parse() retorna Result
let numero: Result<i32, ParseIntError> = "42".parse();
println!("{numero:?}"); // Ok(42)
let invalido: Result<i32, ParseIntError> = "abc".parse();
println!("{invalido:?}"); // Err(invalid digit found in string)
}
Tabela de Métodos Principais
| Método | Descrição | Exemplo |
|---|---|---|
is_ok() | Retorna true se é Ok | Ok(1).is_ok() → true |
is_err() | Retorna true se é Err | Err("x").is_err() → true |
ok() | Converte para Option<T> | Ok(1).ok() → Some(1) |
err() | Converte para Option<E> | Err("x").err() → Some("x") |
unwrap() | Extrai o valor ou panic | Ok(1).unwrap() → 1 |
unwrap_err() | Extrai o erro ou panic | Err("x").unwrap_err() → "x" |
unwrap_or(default) | Extrai ou retorna default | Err("x").unwrap_or(0) → 0 |
unwrap_or_else(f) | Extrai ou calcula via closure | Err("x").unwrap_or_else(|_| 0) |
unwrap_or_default() | Extrai ou usa Default | Err("x").unwrap_or_default() → 0 |
expect(msg) | Como unwrap com mensagem | res.expect("falhou") |
map(f) | Transforma o valor Ok | Ok(1).map(|x| x * 2) → Ok(2) |
map_err(f) | Transforma o valor Err | Err(1).map_err(|e| e + 1) → Err(2) |
and_then(f) | Encadeia operações Result | Ok(1).and_then(|x| Ok(x + 1)) |
or_else(f) | Alternativa se for Err | Err(1).or_else(|_| Ok(0)) |
as_ref() | Result<&T, &E> a partir de &Result | res.as_ref() |
inspect(f) | Executa closure sem consumir (Rust 1.76+) | res.inspect(|v| println!("{v}")) |
inspect_err(f) | Inspeciona o erro sem consumir | res.inspect_err(|e| log::error!("{e}")) |
Exemplos Práticos
1. Propagação de Erros com ?
O operador ? é a forma idiomática de propagar erros em Rust. Ele extrai o valor de Ok ou retorna Err imediatamente da função.
use std::fs;
use std::io;
use std::num::ParseIntError;
#[derive(Debug)]
enum AppErro {
Io(io::Error),
Parse(ParseIntError),
}
impl From<io::Error> for AppErro {
fn from(e: io::Error) -> Self {
AppErro::Io(e)
}
}
impl From<ParseIntError> for AppErro {
fn from(e: ParseIntError) -> Self {
AppErro::Parse(e)
}
}
fn ler_numero_do_arquivo(caminho: &str) -> Result<i32, AppErro> {
let conteudo = fs::read_to_string(caminho)?; // io::Error → AppErro
let numero = conteudo.trim().parse::<i32>()?; // ParseIntError → AppErro
Ok(numero)
}
fn main() {
match ler_numero_do_arquivo("numero.txt") {
Ok(n) => println!("Número lido: {n}"),
Err(AppErro::Io(e)) => eprintln!("Erro de I/O: {e}"),
Err(AppErro::Parse(e)) => eprintln!("Erro de parse: {e}"),
}
}
2. Transformando Erros com map_err
fn validar_idade(input: &str) -> Result<u8, String> {
let idade: u8 = input
.trim()
.parse()
.map_err(|e| format!("'{input}' não é uma idade válida: {e}"))?;
if idade < 1 || idade > 150 {
return Err(format!("Idade {idade} fora do intervalo permitido (1-150)"));
}
Ok(idade)
}
fn main() {
let testes = vec!["25", "abc", "200", "0", "30"];
for entrada in testes {
match validar_idade(entrada) {
Ok(idade) => println!("{entrada} → Idade válida: {idade}"),
Err(e) => println!("{entrada} → Erro: {e}"),
}
}
}
3. Coletando Results em um Vec
fn main() {
let entradas = vec!["1", "2", "3", "4", "5"];
// collect() que falha no primeiro erro
let todos: Result<Vec<i32>, _> = entradas
.iter()
.map(|s| s.parse::<i32>())
.collect();
println!("Todos: {todos:?}"); // Ok([1, 2, 3, 4, 5])
// Com um valor inválido
let com_erro: Result<Vec<i32>, _> = vec!["1", "abc", "3"]
.iter()
.map(|s| s.parse::<i32>())
.collect();
println!("Com erro: {com_erro:?}"); // Err(...)
// Separando sucessos e falhas com partition
let misturado = vec!["10", "xyz", "20", "abc", "30"];
let (ok, erros): (Vec<_>, Vec<_>) = misturado
.iter()
.map(|s| s.parse::<i32>())
.partition(Result::is_ok);
let numeros: Vec<i32> = ok.into_iter().map(Result::unwrap).collect();
let falhas: Vec<String> = erros
.into_iter()
.map(|e| e.unwrap_err().to_string())
.collect();
println!("Números: {numeros:?}"); // [10, 20, 30]
println!("Erros: {falhas:?}");
}
4. Tipo de Erro Customizado com thiserror
// Usando derivação manual (sem crate externo)
use std::fmt;
use std::io;
#[derive(Debug)]
enum ConfigErro {
ArquivoNaoEncontrado(io::Error),
ChaveAusente(String),
ValorInvalido { chave: String, valor: String },
}
impl fmt::Display for ConfigErro {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ConfigErro::ArquivoNaoEncontrado(e) => {
write!(f, "Arquivo de configuração não encontrado: {e}")
}
ConfigErro::ChaveAusente(chave) => {
write!(f, "Chave obrigatória ausente: '{chave}'")
}
ConfigErro::ValorInvalido { chave, valor } => {
write!(f, "Valor inválido '{valor}' para a chave '{chave}'")
}
}
}
}
impl std::error::Error for ConfigErro {}
impl From<io::Error> for ConfigErro {
fn from(e: io::Error) -> Self {
ConfigErro::ArquivoNaoEncontrado(e)
}
}
fn carregar_porta(config: &std::collections::HashMap<String, String>) -> Result<u16, ConfigErro> {
let valor = config
.get("porta")
.ok_or_else(|| ConfigErro::ChaveAusente("porta".to_string()))?;
valor.parse::<u16>().map_err(|_| ConfigErro::ValorInvalido {
chave: "porta".to_string(),
valor: valor.clone(),
})
}
fn main() {
use std::collections::HashMap;
let mut config = HashMap::new();
config.insert("porta".to_string(), "8080".to_string());
match carregar_porta(&config) {
Ok(porta) => println!("Porta: {porta}"),
Err(e) => eprintln!("Erro: {e}"),
}
// Testando com valor inválido
config.insert("porta".to_string(), "abc".to_string());
match carregar_porta(&config) {
Ok(porta) => println!("Porta: {porta}"),
Err(e) => eprintln!("Erro: {e}"),
}
}
5. Encadeamento Fluente com and_then
fn parse_coordenada(input: &str) -> Result<(f64, f64), String> {
let partes: Vec<&str> = input.split(',').collect();
if partes.len() != 2 {
return Err(format!("Esperado 'lat,lon', recebido: '{input}'"));
}
let lat = partes[0].trim().parse::<f64>()
.map_err(|e| format!("Latitude inválida: {e}"))?;
let lon = partes[1].trim().parse::<f64>()
.map_err(|e| format!("Longitude inválida: {e}"))?;
validar_latitude(lat)
.and_then(|lat| validar_longitude(lon).map(|lon| (lat, lon)))
}
fn validar_latitude(lat: f64) -> Result<f64, String> {
if (-90.0..=90.0).contains(&lat) {
Ok(lat)
} else {
Err(format!("Latitude {lat} fora do intervalo [-90, 90]"))
}
}
fn validar_longitude(lon: f64) -> Result<f64, String> {
if (-180.0..=180.0).contains(&lon) {
Ok(lon)
} else {
Err(format!("Longitude {lon} fora do intervalo [-180, 180]"))
}
}
fn main() {
let testes = vec![
"-23.5505, -46.6333", // São Paulo
"91.0, 0.0", // Latitude inválida
"abc, 10.0", // Parse error
"0.0", // Formato inválido
];
for entrada in testes {
match parse_coordenada(entrada) {
Ok((lat, lon)) => println!("({lat}, {lon}) - Válido"),
Err(e) => println!("'{entrada}' - Erro: {e}"),
}
}
}
Dicas de Performance e Armadilhas
Use
?ao invés dematchpara propagação: O operador?é mais conciso e idiomático. Ele também chamaFrom::fromautomaticamente para converter tipos de erro.Evite
unwrap()em código de produção: Useexpect()no mínimo (para documentar por que o valor deveria ser Ok) ou, melhor ainda, propague o erro com?.Box<dyn Error>para prototipagem rápida: Emmain()ou scripts rápidos, useResult<T, Box<dyn std::error::Error>>para aceitar qualquer tipo de erro sem criar enums personalizados.map_errpara contexto adicional: Sempre que propagar um erro, considere usarmap_errpara adicionar contexto sobre o que estava sendo feito quando o erro ocorreu.Result é um iterador:
ResultimplementaIntoIterator—Ok(v)produz um iterador com um elemento,Errproduz um iterador vazio. Isso é útil comflat_mapechain.?em main(): Desde Rust 1.26,main()pode retornarResult. Basta declararfn main() -> Result<(), Box<dyn std::error::Error>>.
Veja Também
- Tratamento de Erros em Rust — tutorial completo
- Tratar Erros em Rust — receitas práticas
- Option<T>: Some e None — para valores opcionais sem erro
- Iterator Trait — collect() converte iteradores de Results
- String e &str —
parse()retorna Result - Erros Comuns em Rust — soluções para mensagens de erro frequentes
- Cheatsheet Rust — referência rápida de sintaxe
- Documentação oficial — std::result::Result