Introdução
Code review é uma parte essencial do desenvolvimento profissional. No ecossistema Rust, onde a segurança de memória e o desempenho são prioridades, a revisão de código adquire uma importância especial. O uso incorreto de unsafe, a ignorância das regras de ownership ou panics implícitos podem anular todas as vantagens da linguagem.
Neste artigo, vamos analisar o que observar durante um code review em Rust, como estruturar o feedback e quais ferramentas podem ajudar a automatizar as verificações. O material será útil tanto para iniciantes quanto para desenvolvedores experientes que desejam organizar o processo de revisão.
1. Segurança: verificação de código unsafe e panics implícitos
Rust garante segurança de memória, mas unsafe é uma renúncia a essas garantias. Cada bloco unsafe deve ser cuidadosamente verificado.
O que verificar em código unsafe?
- Correção dos ponteiros: se há desreferenciamento de ponteiro nulo ou dangling pointer.
- Alinhamento de dados: se o alinhamento está correto ao fazer cast de raw pointers.
- Aliasing: se há violações das regras de aliasing (por exemplo, borrow mutável e imutável simultâneo).
- Invariantes: se os invariantes das estruturas de dados são mantidos (por exemplo, um heap binário continua sendo um heap).
// Exemplo ruim: cast inseguro sem verificação de alinhamentofn bad_unsafe(data: &[u8]) -> &[u32] { unsafe { std::slice::from_raw_parts( data.as_ptr() as *const u32, data.len() / 4 ) }}
// Exemplo bom: com verificação de alinhamentofn good_unsafe(data: &[u8]) -> Option<&[u32]> { if data.len() % 4 != 0 || data.as_ptr() as usize % 4 != 0 { return None; } Some(unsafe { std::slice::from_raw_parts( data.as_ptr() as *const u32, data.len() / 4 ) })}Panics implícitos
Em Rust, muitas operações podem causar panic: unwrap(), expect(), panic!(), divisão por zero, acesso fora dos limites de um array. No code review, deve-se prestar atenção a:
- Use
unwrap()apenas em testes ou quando o erro é realmente impossível. - Prefira
unwrap_or(),unwrap_or_else()ou tratamento viamatch. - Verifique se todas as conversões com
asnão perdem dados (por exemplo,u64 as u32).
// Ruim: panic implícitolet value = some_option.unwrap();
// Bom: tratamento explícitolet value = some_option.unwrap_or_else(|| { log::error!("Missing expected value"); default_value()});2. Ownership e borrowing: erros típicos
O sistema de ownership é a principal característica do Rust. Os erros do compilador aqui são os melhores amigos, mas o code review ajuda a encontrar erros lógicos que o compilador não detecta.
Clonagens desnecessárias
Um problema comum é clonar onde se poderia passar uma referência. Isso reduz o desempenho e aumenta o uso de memória.
// Ruim: clonagem de toda a stringfn process(data: Vec<String>) { for item in data { let cloned = item.clone(); do_something(&cloned); }}
// Bom: passagem de referênciafn process(data: &[String]) { for item in data { do_something(item); }}Borrows e lifetimes desnecessários
Verifique se o tempo de vida das referências não é muito longo. Às vezes, é possível simplificar a assinatura da função removendo anotações de lifetime extras.
// Complexo: lifetimes desnecessáriosfn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y }}
// Se