<h2>Introdução às Estratégias de Recuperação em Rust</h2>
<p>Rust oferece um sistema de tratamento de erros único entre linguagens modernas. Diferentemente de exceções tradicionais, Rust utiliza tipos <code>Result<T, E></code> e <code>Option<T></code> para forçar o tratamento explícito de falhas em tempo de compilação. Isso elimina aquele problema clássico: "esqueci de tratar o erro e meu programa crasheou em produção". Nesta aula, você aprenderá as principais estratégias para recuperação robusta de falhas, desde padrões básicos até técnicas avançadas de resiliência.</p>
<h2>Fundamentos: Result e Option</h2>
<h3>Compreendendo Result<T, E></h3>
<p>O tipo <code>Result</code> é um enum que representa sucesso (<code>Ok(T)</code>) ou falha (<code>Err(E)</code>). Todo sistema de recuperação em Rust começa aqui. Você pode desembrulhar manualmente, usar operadores combinadores ou propagar erros para a função chamadora.</p>
<pre><code class="language-rust">use std::fs::File;
use std::io::Read;
fn ler_arquivo(caminho: &str) -> Result<String, std::io::Error> {
let mut arquivo = File::open(caminho)?;
let mut conteudo = String::new();
arquivo.read_to_string(&mut conteudo)?;
Ok(conteudo)
}
fn main() {
match ler_arquivo("dados.txt") {
Ok(conteudo) => println!("Sucesso: {}", conteudo),
Err(erro) => eprintln!("Erro na leitura: {}", erro),
}
}</code></pre>
<p>O operador <code>?</code> propaga o erro automaticamente — se <code>File::open</code> falhar, a função retorna imediatamente com esse erro. Isso é muito mais limpo que verificações aninhadas.</p>
<h3>Manipulação com map e and_then</h3>
<p>Os combinadores funcionais permitem transformar valores e encadear operações sem desembrulhar manualmente. <code>map</code> transforma o valor Ok, enquanto <code>and_then</code> permite operações que retornam outro <code>Result</code>.</p>
<pre><code class="language-rust">fn processar_numero(texto: &str) -> Result<i32, String> {
texto
.parse::<i32>()
.map( | n | n * 2) .map_err(|_| "Falha ao converter string para número".to_string()) .and_then(|n| {
if n > 100 {
Ok(n)
} else {
Err("Número muito pequeno".to_string())
}
})
}
fn main() {
println!("{:?}", processar_numero("50")); // Ok(100)
println!("{:?}", processar_numero("abc")); // Err("Falha ao converter...")
}</code></pre>
<h2>Padrões Avançados de Recuperação</h2>
<h3>Retry com Backoff Exponencial</h3>
<p>Em sistemas distribuídos, falhas temporárias são comuns. Uma estratégia eficaz é retornar automaticamente com espera crescente. Isso é crítico para conexões de rede ou acesso a APIs.</p>
<pre><code class="language-rust">use std::thread;
use std::time::Duration;
fn executar_com_retry<F, T, E>(
mut funcao: F,
max_tentativas: u32,
) -> Result<T, E>
where
F: FnMut() -> Result<T, E>,
{
for tentativa in 1..=max_tentativas {
match funcao() {
Ok(resultado) => return Ok(resultado),
Err(erro) => {
if tentativa == max_tentativas {
return Err(erro);
}
let espera = Duration::from_millis(2_u64.pow(tentativa - 1) * 100);
thread::sleep(espera);
}
}
}
unreachable!()
}
fn main() {
let mut contador = 0;
let resultado = executar_com_retry(
|| {
contador += 1;
if contador < 3 {
Err("Falha temporária")
} else {
Ok("Sucesso!")
}
},
5,
);
println!("{:?}", resultado); // Ok("Sucesso!")
}</code></pre>
<h3>Circuit Breaker Pattern</h3>
<p>Quando um serviço está frequentemente falhando, continuar tentando é inútil. O padrão Circuit Breaker detecta falhas consecutivas e "abre o circuito", rejeitando requisições rapidamente até a recuperação.</p>
<pre><code class="language-rust">use std::sync::{Arc, Mutex};
enum EstadoCircuito {
Fechado,
Aberto { falhas_consecutivas: u32 },
MeioAberto,
}
struct CircuitBreaker {
estado: Arc<Mutex<EstadoCircuito>>,
limiar_falhas: u32,
}
impl CircuitBreaker {
fn novo(limiar: u32) -> Self {
CircuitBreaker {
estado: Arc::new(Mutex::new(EstadoCircuito::Fechado)),
limiar_falhas: limiar,
}
}
fn executar<F, T>(&self, funcao: F) -> Result<T, String>
where
F: FnOnce() -> Result<T, String>,
{
let mut estado = self.estado.lock().unwrap();
match *estado {
EstadoCircuito::Aberto { falhas_consecutivas } if falhas_consecutivas > 0 => {
return Err("Circuito aberto - rejeitando requisição".to_string());
}
_ => {}
}
match funcao() {
Ok(resultado) => {
*estado = EstadoCircuito::Fechado;
Ok(resultado)
}
Err(erro) => {
if let EstadoCircuito::Aberto { falhas_consecutivas } = *estado {
*estado = EstadoCircuito::Aberto {
falhas_consecutivas: falhas_consecutivas + 1,
};
if falhas_consecutivas >= self.limiar_falhas {
return Err("Circuito aberto".to_string());
}
} else {
*estado = EstadoCircuito::Aberto { falhas_consecutivas: 1 };
}
Err(erro)
}
}
}
}</code></pre>
<h2>Logging, Recuperação Graceful e Panic!</h2>
<h3>Tratamento Contextualizado com Custom Errors</h3>
<p>Erros bem estruturados facilitam debug e recuperação. Crie tipos de erro customizados com contexto rico.</p>
<pre><code class="language-rust">use std::fmt;
#[derive(Debug)]
enum ErroApp {
Io(std::io::Error),
Parse(std::num::ParseIntError),
Validacao(String),
}
impl fmt::Display for ErroApp {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ErroApp::Io(e) => write!(f, "Erro de I/O: {}", e),
ErroApp::Parse(e) => write!(f, "Erro de parsing: {}", e),
ErroApp::Validacao(msg) => write!(f, "Validação falhou: {}", msg),
}
}
}
impl From<std::io::Error> for ErroApp {
fn from(erro: std::io::Error) -> Self {
ErroApp::Io(erro)
}
}
impl From<std::num::ParseIntError> for ErroApp {
fn from(erro: std::num::ParseIntError) -> Self {
ErroApp::Parse(erro)
}
}
fn processar_dados(entrada: &str) -> Result<i32, ErroApp> {
let numero = entrada.parse::<i32>()?;
if numero < 0 {
return Err(ErroApp::Validacao("Número deve ser positivo".to_string()));
}
Ok(numero)
}</code></pre>
<h3>Quando Usar panic!</h3>
<p><code>panic!</code> é para programas irrecuperáveis ou bugs no código. Nunca use para tratar erros normais de negócio. Use quando precisa falhar rapidamente durante desenvolvimento ou para violações de invariantes.</p>
<pre><code class="language-rust">fn divisao_segura(a: i32, b: i32) -> i32 {
assert!(b != 0, "Divisor não pode ser zero");
a / b
}</code></pre>
<h2>Conclusão</h2>
<p>Três pontos-chave para dominar recuperação de falhas em Rust: <strong>primeiro</strong>, sempre prefira <code>Result<T, E></code> para erros esperados — o compilador força tratamento explícito, eliminando surpresas. <strong>Segundo</strong>, use padrões como retry com backoff e circuit breaker para sistemas distribuídos — eles transformam falhas temporárias em sucesso. <strong>Terceiro</strong>, crie tipos de erro customizados com contexto rico para facilitar debug e manutenção — seu time futuro agradecerá.</p>
<h2>Referências</h2>
<ul>
<li>https://doc.rust-lang.org/book/ch09-00-error-handling.html</li>
<li>https://docs.rs/anyhow/latest/anyhow/</li>
<li>https://docs.rs/thiserror/latest/thiserror/</li>
<li>https://rust-lang.github.io/api-guidelines/type-safety.html</li>
<li>https://tokio.rs/tokio/tutorial</li>
</ul>