Rust Admin

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

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

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 Result<T, E> e Option<T> 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 Result é um enum que representa sucesso (Ok(T)) ou falha (Err(E)). 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.

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),
    }
}

O operador ? propaga o erro automaticamente — se File::open falhar, a função retorna imediatamente com esse erro. Isso é muito mais limpo que verificações aninhadas.

Manipulação com map e and_then

Os combinadores funcionais permitem transformar valores e encadear operações sem desembrulhar manualmente. map transforma o valor Ok, enquanto and_then permite operações que retornam outro Result.

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...")
}

Padrões Avançados de Recuperação

Retry com Backoff Exponencial

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.

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!")
}

Circuit Breaker Pattern

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.

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)
            }
        }
    }
}

Logging, Recuperação Graceful e Panic!

Tratamento Contextualizado com Custom Errors

Erros bem estruturados facilitam debug e recuperação. Crie tipos de erro customizados com contexto rico.

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)
}

Quando Usar panic!

panic! é 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.

fn divisao_segura(a: i32, b: i32) -> i32 {
    assert!(b != 0, "Divisor não pode ser zero");
    a / b
}

Conclusão

Três pontos-chave para dominar recuperação de falhas em Rust: primeiro, sempre prefira Result<T, E> para erros esperados — o compilador força tratamento explícito, eliminando surpresas. Segundo, use padrões como retry com backoff e circuit breaker para sistemas distribuídos — eles transformam falhas temporárias em sucesso. Terceiro, crie tipos de erro customizados com contexto rico para facilitar debug e manutenção — seu time futuro agradecerá.

Referências

  • https://doc.rust-lang.org/book/ch09-00-error-handling.html
  • https://docs.rs/anyhow/latest/anyhow/
  • https://docs.rs/thiserror/latest/thiserror/
  • https://rust-lang.github.io/api-guidelines/type-safety.html
  • https://tokio.rs/tokio/tutorial

Artigos relacionados