Introducción
Rust es un potente lenguaje de programación de sistemas que está ganando popularidad gracias a su seguridad y rendimiento. Sin embargo, su sistema de ownership (propiedad) y borrowing (préstamo) a menudo desconcierta a los principiantes. En este artículo analizaremos los principales errores de los desarrolladores principiantes de Rust para que puedas evitarlos y familiarizarte más rápido con el lenguaje. Revisaremos problemas típicos con borrowing, lifetimes (tiempos de vida), strings y gestión de memoria.
1. Ignorar las reglas de borrowing (Borrow Checker)
El error más común es intentar tener simultáneamente una referencia mutable y una inmutable a los mismos datos. Rust lo prohíbe estrictamente en tiempo de compilación.
Problema: referencias múltiples
fn main() {
let mut data = vec![1, 2, 3];
let ref1 = &data; // referencia inmutable
let ref2 = &mut data; // referencia mutable — ¡ERROR!
println!("{} {}", ref1[0], ref2[0]);
}
El compilador mostrará un error: cannot borrow `data` as mutable because it is also borrowed as immutable.
Solución: ámbito correcto
fn main() {
let mut data = vec![1, 2, 3];
{
let ref1 = &data; // referencia inmutable
println!("{}", ref1[0]);
} // ref1 sale del ámbito
let ref2 = &mut data; // ahora se puede
ref2[0] = 10;
println!("{}", ref2[0]);
}
Siempre controla el tiempo de vida de las referencias. Si necesitas trabajar con una referencia mutable y otra inmutable, sepáralas en diferentes bloques.
2. Trabajo incorrecto con strings: String vs &str
Los principiantes a menudo confunden String (tipo con ownership, mutable) y &str (referencia a un slice de string). Esto provoca errores al pasar strings a funciones.
Problema: esperar ownership en lugar de referencia
fn print_length(s: String) {
println!("Longitud: {}", s.len());
}
fn main() {
let name = "Alice".to_string();
print_length(name);
println!("Nombre: {}", name); // ¡ERROR! name fue movido
}
Solución: usa &str para parámetros
fn print_length(s: &str) {
println!("Longitud: {}", s.len());
}
fn main() {
let name = "Alice".to_string();
print_length(&name); // pasamos una referencia
println!("Nombre: {}", name); // ahora funciona
}
Regla: si la función no debe tomar ownership, usa &str. Esto hace el código flexible — puedes pasar tanto &String como literales de string "texto".
3. Errores con lifetimes (tiempos de vida)
Cuando una función devuelve una referencia, Rust requiere especificar cuánto tiempo vivirá esa referencia. Los principiantes a menudo olvidan las anotaciones de lifetimes.
Problema: lifetimes implícitos
fn longest(x: &str, y: &str) -> &str { // ERROR: missing lifetime specifier
if x.len() > y.len() { x } else { y }
}
Solución: añade un lifetime común
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
fn main() {
let s1 = String::from("corto");
let s2 = "cadena larga";
let result = longest(&s1, &s2);
println!("La más larga: {}", result);
}
La anotación 'a le dice al compilador que la referencia devuelta vivirá no más que el parámetro de entrada más corto. Esto garantiza la seguridad de la memoria.
4. Ignorar Option y Result
Rust no tiene null. En su lugar se usa Option<T> para la posible ausencia de un valor y Result<T, E> para el manejo de errores. Los principiantes a menudo intentan desreferenciar Option sin verificación.
Problema: pánico con unwrap
fn find_user(id: u32) -> Option<String> {
if id == 1 {
Some("Anna".to_string())
}