Introducción: por qué Rust parece difícil, pero vale la pena
Rust es un lenguaje que promete gestión segura de memoria sin recolector de basura y alto rendimiento. Sin embargo, estas mismas ventajas se convierten en un obstáculo para los principiantes. El compilador de Rust es estricto hasta el más mínimo detalle: no perdona comportamientos indefinidos, punteros colgantes ni condiciones de carrera. En este artículo analizaremos los principales errores de los desarrolladores principiantes de Rust, desde los más comunes hasta los más engañosos. Aprenderás cómo evitarlos y acelerar tu camino hacia el dominio del lenguaje.
1. La lucha con el Borrow Checker
El "dolor" más conocido para los principiantes es el sistema de ownership (propiedad) y borrowing (préstamo). El compilador no te permite crear simplemente una referencia a datos si estos pueden ser modificados o eliminados.
1.1. Intentar modificar datos a través de una referencia inmutable
Uno de los primeros errores es intentar modificar una variable sobre la que ya existe una referencia inmutable (&).
let mut x = 5;let y = &x; // referencia inmutablelet z = &mut x; // ERROR: no se puede crear una referencia mutable mientras exista una inmutableprintln!("{}", y);Cómo solucionarlo: Asegúrate de que el ámbito de las referencias inmutables haya terminado antes de crear una mutable. Usa llaves para delimitar el ámbito.
let mut x = 5;{ let y = &x; // el ámbito de y está limitado println!("{}", y);}let z = &mut x; // ahora se puede1.2. Devolver una referencia a una variable local
Los principiantes a menudo intentan devolver una referencia a datos creados dentro de una función. Esto provoca el error "dangling reference".
fn get_ref() -> &String { let s = String::from("hello"); &s // ERROR: s será eliminada al salir de la función}Cómo solucionarlo: Devuelve la propiedad (el string en sí) o usa un tiempo de vida estático ('static) si los datos viven para siempre.
fn get_string() -> String { let s = String::from("hello"); s // transferimos la propiedad}2. Uso incorrecto de lifetimes (tiempos de vida)
Cuando empiezas a trabajar con funciones que aceptan múltiples referencias, el compilador exige que especifiques cómo se relacionan en términos de tiempo de vida. Los principiantes o ignoran los lifetimes, o escriben 'static en todas partes donde no es necesario.
2.1. Omitir anotaciones de lifetimes en las firmas
Si una función acepta dos referencias y devuelve una, Rust no puede determinar por sí solo cuál de ellas "vive" más tiempo.
fn longest(x: &str, y: &str) -> &str { // ERROR: necesita un lifetime if x.len() > y.len() { x } else { y }}Cómo solucionarlo: Agrega un parámetro de tiempo de vida común.
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y }}2.2. Uso excesivo de 'static
'static significa que la referencia vive durante todo el programa. Usarlo para literales de cadena está bien, pero para datos dinámicos es una mala práctica.
fn get_static() -> &'static str { "hello" // correcto}
fn get_dynamic() -> &'static str { let s = String::from("hello"); &s // ERROR: s no vive para siempre}Consejo: Intenta no usar 'static sin necesidad. Es mejor transferir la propiedad o usar Arc<Mutex<T>> para concurrencia.
3. Abuso de unwrap() y pánico
Rust fomenta el manejo explícito de errores mediante Result y Option. Pero los principiantes suelen usar .unwrap() o .expect() en todas partes para "callar" al compilador. Esto provoca pánico en tiempo de ejecución.