Introducción: ¿Por qué Rust es tan estricto y cómo el code review ayuda a no volverse loco?
\nHas escrito código en Rust. ¡Se compiló, hurra! Pero aquí está el problema: el desarrollador senior, al abrir tu Pull Request, suspira y empieza a escribir comentarios. ¿Te suena? Rust es un lenguaje con una filosofía única: confía en ti, pero lo verifica todo. Sin embargo, el compilador no ve lo que ve un ojo experimentado. No te dirá: «Oye, esto se puede hacer de forma más elegante» o «Tu código tiene fugas, no de memoria, sino de rendimiento».
\nEn este artículo, realizaremos una revisión de código en Rust y analizaremos las observaciones típicas de los seniors. Aprenderás cómo convertir «código que simplemente funciona» en código idiomático y fiable. ¡Allá vamos!
\n\n1. Ignorar el sistema de tipos: «Vamos a envolver todo en String»
\nEl error más común de los principiantes es usar String o tipos genéricos donde se necesitan tipos específicos. Un senior te dirá: «Tu tipo debe reflejar la lógica de negocio, no ser solo una cadena».
Problema: pérdida de semántica
\nImagina que escribes una función para trabajar con direcciones de correo electrónico:
\nfn send_email(to: String, subject: String, body: String) {\n // ...\n}\n¿Qué hay de malo aquí? Puedes confundir to y subject — el compilador no lo notará. Un senior sugerirá usar el patrón newtype:
struct Email(String);\nstruct Subject(String);\nstruct Body(String);\n\nfn send_email(to: Email, subject: Subject, body: Body) {\n // ...\n}\n¿Parece excesivo? En un code review de Rust, este enfoque es el estándar de oro. Hace que el código sea autodocumentado y elimina toda una clase de errores.
\n\n2. Abuso de clone(): copiar no significa poseer
\nLos desarrolladores junior suelen usar .clone() en todas partes donde el compilador se queja por los préstamos. Un senior te dirá: «Simplemente te estás rindiendo ante el borrow checker. Piensa de otra manera».
Ejemplo: clone innecesario
\nfn process(data: Vec<i32>) {\n let first = data[0].clone();\n let second = data[1].clone();\n // ...\n}\ni32 implementa el trait Copy — clone() aquí es completamente innecesario. Pero el problema es más profundo: si clonas estructuras grandes (por ejemplo, Vec o String), obtienes un golpe al rendimiento.
El enfoque correcto es trabajar con referencias:
\nfn process(data: &[i32]) {\n let first = &data[0];\n let second = &data[1];\n // ...\n}\nEn una revisión de código en Rust, debes explicar por qué estás clonando. Si la respuesta es «para que el compilador no se queje» — prepárate para un comentario demoledor.
\n\n3. Mal manejo de errores: .unwrap() no es una solución definitiva
\nLos seniors odian unwrap() y expect() en código de producción. ¿Por qué? Porque convierten un error potencialmente recuperable en un pánico. Y un pánico en Rust es una terminación abrupta.
¿Qué tiene de malo este código?
\nlet file_content = std::fs::read_to_string("config.txt").unwrap();\nSi el archivo no existe, el programa fallará. Un senior sugerirá:
\nlet file_content = std::fs::read_to_string("config.txt")\n .map_err(|e| format!("Failed to read config: {}", e))?;\nUsa el operador ? y tus propios tipos de error. Esta es la base del Rust idiomático. En un code review de Rust, esta es una de las primeras observaciones.
4. Rendimiento olvidado: asignaciones e iteradores
\nRust es un lenguaje de sistemas. Un senior no solo mirará la seguridad, sino también la velocidad. Uno de los problemas típicos es la creación de colecciones innecesarias.
\nEjemplo: coleccionar sin necesidad
\nlet numbers: Vec<i32> = (0..100).collect();\nlet doubled: Vec<i32> = numbers.iter().map(|x| x * 2).collect();