Rust

Como Usar Estratégias de Recuperação de Falhas em Sistemas Rust em Produção

8 min de leitura

Como Usar Estratégias de Recuperação de Falhas em Sistemas Rust em Produção

Introdução às Estratégias de Recuperação em Rust Rust oferece um sistema de tratamento de erros único entre linguagens modernas. Diferentemente de exceções tradicionais, Rust utiliza tipos e 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. Fundamentos: Result e Option Compreendendo Result O tipo é um enum que representa sucesso ( ) ou falha ( ). 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. O operador propaga o erro automaticamente — se falhar, a função retorna imediatamente com esse erro. Isso é muito mais limpo que verificações aninhadas. Manipulação com map e andthen Os combinadores funcionais permitem transformar valores e encadear operações sem desembrulhar manualmente. transforma o valor

<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&lt;T, E&gt;</code> e <code>Option&lt;T&gt;</code> para forçar o tratamento explícito de falhas em tempo de compilação. Isso elimina aquele problema clássico: &quot;esqueci de tratar o erro e meu programa crasheou em produção&quot;. 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&lt;T, E&gt;</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: &amp;str) -&gt; Result&lt;String, std::io::Error&gt; {

let mut arquivo = File::open(caminho)?;

let mut conteudo = String::new();

arquivo.read_to_string(&amp;mut conteudo)?;

Ok(conteudo)

}

fn main() {

match ler_arquivo(&quot;dados.txt&quot;) {

Ok(conteudo) =&gt; println!(&quot;Sucesso: {}&quot;, conteudo),

Err(erro) =&gt; eprintln!(&quot;Erro na leitura: {}&quot;, 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: &amp;str) -&gt; Result&lt;i32, String&gt; {

texto

.parse::&lt;i32&gt;()

.map( | n | n * 2) .map_err(|_| &quot;Falha ao converter string para número&quot;.to_string()) .and_then(|n| {

if n &gt; 100 {

Ok(n)

} else {

Err(&quot;Número muito pequeno&quot;.to_string())

}

})

}

fn main() {

println!(&quot;{:?}&quot;, processar_numero(&quot;50&quot;)); // Ok(100)

println!(&quot;{:?}&quot;, processar_numero(&quot;abc&quot;)); // Err(&quot;Falha ao converter...&quot;)

}</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&lt;F, T, E&gt;(

mut funcao: F,

max_tentativas: u32,

) -&gt; Result&lt;T, E&gt;

where

F: FnMut() -&gt; Result&lt;T, E&gt;,

{

for tentativa in 1..=max_tentativas {

match funcao() {

Ok(resultado) =&gt; return Ok(resultado),

Err(erro) =&gt; {

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 &lt; 3 {

Err(&quot;Falha temporária&quot;)

} else {

Ok(&quot;Sucesso!&quot;)

}

},

5,

);

println!(&quot;{:?}&quot;, resultado); // Ok(&quot;Sucesso!&quot;)

}</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 &quot;abre o circuito&quot;, 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&lt;Mutex&lt;EstadoCircuito&gt;&gt;,

limiar_falhas: u32,

}

impl CircuitBreaker {

fn novo(limiar: u32) -&gt; Self {

CircuitBreaker {

estado: Arc::new(Mutex::new(EstadoCircuito::Fechado)),

limiar_falhas: limiar,

}

}

fn executar&lt;F, T&gt;(&amp;self, funcao: F) -&gt; Result&lt;T, String&gt;

where

F: FnOnce() -&gt; Result&lt;T, String&gt;,

{

let mut estado = self.estado.lock().unwrap();

match *estado {

EstadoCircuito::Aberto { falhas_consecutivas } if falhas_consecutivas &gt; 0 =&gt; {

return Err(&quot;Circuito aberto - rejeitando requisição&quot;.to_string());

}

_ =&gt; {}

}

match funcao() {

Ok(resultado) =&gt; {

*estado = EstadoCircuito::Fechado;

Ok(resultado)

}

Err(erro) =&gt; {

if let EstadoCircuito::Aberto { falhas_consecutivas } = *estado {

*estado = EstadoCircuito::Aberto {

falhas_consecutivas: falhas_consecutivas + 1,

};

if falhas_consecutivas &gt;= self.limiar_falhas {

return Err(&quot;Circuito aberto&quot;.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(&amp;self, f: &amp;mut fmt::Formatter) -&gt; fmt::Result {

match self {

ErroApp::Io(e) =&gt; write!(f, &quot;Erro de I/O: {}&quot;, e),

ErroApp::Parse(e) =&gt; write!(f, &quot;Erro de parsing: {}&quot;, e),

ErroApp::Validacao(msg) =&gt; write!(f, &quot;Validação falhou: {}&quot;, msg),

}

}

}

impl From&lt;std::io::Error&gt; for ErroApp {

fn from(erro: std::io::Error) -&gt; Self {

ErroApp::Io(erro)

}

}

impl From&lt;std::num::ParseIntError&gt; for ErroApp {

fn from(erro: std::num::ParseIntError) -&gt; Self {

ErroApp::Parse(erro)

}

}

fn processar_dados(entrada: &amp;str) -&gt; Result&lt;i32, ErroApp&gt; {

let numero = entrada.parse::&lt;i32&gt;()?;

if numero &lt; 0 {

return Err(ErroApp::Validacao(&quot;Número deve ser positivo&quot;.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) -&gt; i32 {

assert!(b != 0, &quot;Divisor não pode ser zero&quot;);

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&lt;T, E&gt;</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>

Comentários

Mais em Rust

Guia Completo de Box<T> em Rust: Alocação Explícita no Heap
Guia Completo de Box<T> em Rust: Alocação Explícita no Heap

O que é Box e Por Que Usar? Box é um tipo de dado inteligente (smart pointer)...

Dominando Slices em Rust: Referências para Partes de Coleções em Projetos Reais
Dominando Slices em Rust: Referências para Partes de Coleções em Projetos Reais

Entendendo Slices em Rust Um slice é uma referência a uma parte contígua de u...

O que Todo Dev Deve Saber sobre HashMap e HashSet em Rust: Estruturas de Dados por Chave
O que Todo Dev Deve Saber sobre HashMap e HashSet em Rust: Estruturas de Dados por Chave

HashMap: Armazenamento Eficiente com Chaves HashMap é uma estrutura de dados...