Introducción
Code review es una parte integral del desarrollo profesional. En el ecosistema de Rust, donde la seguridad de memoria y el rendimiento son prioritarios, la revisión de código adquiere una importancia especial. El uso incorrecto de unsafe, ignorar las reglas de ownership o los panics implícitos pueden anular todas las ventajas del lenguaje.
En este artículo analizaremos en qué aspectos fijarse durante un code review en Rust, cómo estructurar la retroalimentación y qué herramientas ayudan a automatizar las verificaciones. El material será útil tanto para principiantes como para desarrolladores experimentados que deseen organizar el proceso de revisión.
1. Seguridad: verificación de código unsafe y panics implícitos
Rust garantiza la seguridad de memoria, pero unsafe es una renuncia a esas garantías. Cada bloque unsafe debe ser revisado minuciosamente.
¿Qué verificar en código unsafe?
- Corrección de punteros: que no haya desreferencia de punteros nulos o colgantes.
- Alineación de datos: que la alineación sea correcta al convertir punteros raw.
- Aliasing: que no haya violaciones de las reglas de aliasing (por ejemplo, préstamo mutable e inmutable simultáneo).
- Invariantes: que se mantengan los invariantes de las estructuras de datos (por ejemplo, que un heap binario siga siendo un heap).
// Mal ejemplo: conversión insegura sin verificar alineaciónfn bad_unsafe(data: &[u8]) -> &[u32] { unsafe { std::slice::from_raw_parts( data.as_ptr() as *const u32, data.len() / 4 ) }}
// Buen ejemplo: con verificación de alineaciónfn 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
En Rust hay muchas operaciones que pueden causar un panic: unwrap(), expect(), panic!(), división por cero, acceso fuera de los límites de un array. En un code review se debe prestar atención a:
- Usar
unwrap()solo en tests o cuando el error es realmente imposible. - Preferir
unwrap_or(),unwrap_or_else()o el manejo mediantematch. - Verificar que todas las conversiones con
asno pierdan datos (por ejemplo,u64 as u32).
// Mal: panic implícitolet value = some_option.unwrap();
// Bien: manejo explícitolet value = some_option.unwrap_or_else(|| { log::error!("Missing expected value"); default_value()});2. Ownership y borrowing: errores típicos
El sistema de ownership es la característica principal de Rust. Los errores del compilador aquí son los mejores amigos, pero el code review ayuda a encontrar errores lógicos que el compilador no detecta.
Clonaciones excesivas
Un problema común es clonar cuando se podría pasar una referencia. Esto reduce el rendimiento y aumenta el uso de memoria.
// Mal: clonar todo el stringfn process(data: Vec<String>) { for item in data { let cloned = item.clone(); do_something(&cloned); }}
// Bien: pasar una referenciafn process(data: &[String]) { for item in data { do_something(item); }}Préstamos innecesarios y lifetimes
Verifique si el tiempo de vida de las referencias no es demasiado largo. A veces se puede simplificar la firma de una función eliminando anotaciones de lifetime superfluas.
// Complejo: lifetimes excesivosfn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y }}
// Si