Truques para depurar código em Rust mais rápido
Depurar código em Rust pode ser desafiador devido ao sistema de ownership, lifetimes e concorrência. No entanto, com as técnicas certas, é possível reduzir drasticamente o tempo gasto identificando e corrigindo bugs. Este artigo apresenta truques práticos para acelerar seu fluxo de depuração, desde a configuração do ambiente até ferramentas de análise estática.
1. Configuração do Ambiente de Depuração
A escolha do perfil de compilação impacta diretamente a experiência de debug. O perfil debug (padrão) desabilita otimizações e inclui símbolos de depuração, enquanto o perfil release otimiza o código, dificultando o rastreamento.
No Cargo.toml, configure explicitamente:
[profile.dev]
debug = true
opt-level = 0
Para stepping eficiente, use rust-gdb (wrapper do GDB) ou rust-lldb (LLDB configurado para Rust). Exemplo de uso:
rust-gdb target/debug/meu_programa
(gdb) break main
(gdb) run
(gdb) next
O rust-gdb já carrega automaticamente os pretty-printers para tipos Rust, facilitando a inspeção de Vec, String e Option.
2. Logging Estratégico com log e env_logger
Logs bem estruturados substituem múltiplas execuções com println!. Adicione ao Cargo.toml:
[dependencies]
log = "0.4"
env_logger = "0.10"
Configure no main.rs:
fn main() {
env_logger::init();
log::info!("Iniciando aplicação");
processar_dados();
}
fn processar_dados() {
log::debug!("Processando lote de 100 itens");
for i in 0..100 {
log::trace!("Item {} processado", i);
}
}
Execute com filtros seletivos:
RUST_LOG=meu_crate=debug cargo run
RUST_LOG=warn cargo run # Apenas warnings e erros
Para logs condicionais em loops críticos, crie uma macro:
macro_rules! log_cond {
($cond:expr, $($arg:tt)*) => {
if $cond {
log::debug!($($arg)*);
}
};
}
for i in 0..10000 {
log_cond!(i % 1000 == 0, "Progresso: {}%", i / 100);
}
3. Uso de dbg! e eprintln! para Inspeção Rápida
O macro dbg! é superior ao println! para debug rápido porque imprime o nome da variável, valor e localização no código:
fn calcular_media(valores: &[f64]) -> f64 {
let soma: f64 = valores.iter().sum();
dbg!(soma);
let total = valores.len() as f64;
dbg!(total);
soma / total
}
Saída:
[src/main.rs:4] soma = 150.0
[src/main.rs:6] total = 5.0
Use eprintln! em vez de println! para evitar bufferização e mistura com saída padrão:
eprintln!("DEBUG: valor inesperado: {}", valor);
Para remover dbg! em produção, envolva com #[cfg(debug_assertions)]:
#[cfg(debug_assertions)]
dbg!(resultado);
4. Debug com Ferramentas de Análise Estática
cargo clippy detecta bugs comuns antes da execução. Execute regularmente:
cargo clippy -- -D warnings
Configure warnings críticos como erro no Cargo.toml:
[lints.clippy]
unwrap_used = "deny"
panic = "deny"
O rust-analyzer no VS Code ou Neovim fornece hints de tipo, erros em tempo real e sugestões de correção. Ative a opção "check on save" para feedback imediato:
"rust-analyzer.checkOnSave": true
5. Depuração de Erros de Memória e Concorrência
Para vazamentos de memória e uso após liberação, use valgrind:
cargo install cargo-valgrind
cargo valgrind
Para data races, execute testes com thread única:
cargo test -- --test-threads=1
Para simulação avançada de concorrência, use loom:
[dependencies]
loom = "0.7"
Exemplo de teste com loom:
#[test]
fn teste_concorrente() {
loom::model(|| {
let valor = loom::cell::UnsafeCell::new(0);
let t1 = loom::thread::spawn(|| {
*valor.get() = 1;
});
let t2 = loom::thread::spawn(|| {
*valor.get() = 2;
});
t1.join().unwrap();
t2.join().unwrap();
});
}
Para rastrear deadlocks em Mutex, adicione logging:
use std::sync::{Mutex, Arc};
use log::info;
let dados = Arc::new(Mutex::new(Vec::new()));
{
let mut guard = dados.lock().unwrap();
info!("Lock adquirido em thread {:?}", std::thread::current().id());
guard.push(42);
} // Lock liberado aqui
6. Técnicas de Teste para Isolar Bugs
Crie testes unitários que reproduzam cenários específicos:
#[test]
fn test_calculo_media() {
let valores = vec![10.0, 20.0, 30.0];
assert_eq!(calcular_media(&valores), 20.0);
}
#[test]
fn test_lista_vazia() {
let valores: Vec<f64> = vec![];
assert!(calcular_media(&valores).is_nan());
}
Para ver logs durante os testes:
cargo test -- --nocapture
Use #[cfg(test)] para mockar dependências:
#[cfg(test)]
mod tests {
use super::*;
struct MockDatabase;
impl Database for MockDatabase {
fn buscar_usuario(&self, id: u32) -> Option<String> {
if id == 1 {
Some("Alice".to_string())
} else {
None
}
}
}
#[test]
fn test_com_mock() {
let db = MockDatabase;
assert_eq!(processar_usuario(&db, 1), "Alice");
}
}
7. Otimização do Fluxo de Depuração com Scripts
Crie aliases no shell para comandos frequentes:
alias cb='cargo build 2>&1 | head -20'
alias ct='cargo test -- --nocapture'
alias cw='cargo watch -x run'
Use cargo watch para reexecução automática:
cargo install cargo-watch
cargo watch -x "test -- --nocapture"
Script personalizado para rust-lldb com breakpoints:
#!/bin/bash
# save as debug.sh
cargo build
rust-lldb -o "breakpoint set --name main" -o "run" -o "next" target/debug/meu_programa
Execute com ./debug.sh para iniciar a depuração imediatamente.
Conclusão
Combinando configuração adequada do ambiente, logging estratégico, ferramentas de análise estática e testes isolados, você pode reduzir significativamente o tempo de depuração em Rust. Lembre-se de que a prevenção é mais eficiente que a correção — invista em clippy e testes desde o início do projeto.
Referências
- The Rust Programming Language - Debugging — Capítulo oficial sobre tratamento de erros e depuração em Rust.
- Rust CLI Book - Logging — Guia prático sobre logging estratégico com
env_loggerelog. - Clippy Documentation — Documentação completa da ferramenta de linting estático para Rust.
- Loom - Testing Concurrent Rust — Biblioteca para teste de concorrência e detecção de data races.
- Rust GDB Guide — Tutorial de depuração com GDB e LLDB para Rust (foco em embedded, mas conceitos gerais).
- Cargo Watch — Ferramenta para reexecução automática de comandos Cargo ao salvar arquivos.