7 errores críticos que todos cometen con Rust (y cómo evitarlos)
Rust te da poder y seguridad, pero también nuevas trampas comunes. Aquí tienes siete errores frecuentes con ejemplos concretos, por qué son peligrosos y cómo corregirlos rápidamente.
1) Usar unwrap()/expect() por defecto
Por comodidad se recurre a unwrap(). En producción eso causa panics inesperados. Prefiere propagar errores o manejarlos explícitamente.
// Mal: panic si el archivo no existe
use std::fs::File;
let f = File::open("config.toml").unwrap();
Mejor:
// Propaga el error con ? en una función que devuelve Result
use std::fs::File;
use std::io;
fn open_config() -> io::Result {
let f = File::open("config.toml")?;
Ok(f)
}
Si necesitas tolerancia, maneja el error con match o proporciona un fallback.
2) Clonar en exceso (cloning innecesario)
Clonar tipos como String/Vec sin necesidad provoca copias y pérdida de rendimiento. Evita clones usando referencias o Cow cuando convenga.
// Mal: clona innecesariamente
fn print_two(s: String) {
println!("{}", s.clone());
println!("{}", s);
}
Mejor:
// Usar referencias
fn print_two(s: &str) {
println!("{}", s);
println!("{}", s);
}
let owned = String::from("hola");
print_two(&owned);
3) Confundir Rc y Arc (y enviar a hilos tipos no Send)
Rc no es seguro para threads; intentar usarlo en thread::spawn falla en tiempo de compilación o conduce a diseños incorrectos. Usa Arc para compartir entre hilos.
// Mal: Rc no implementa Send/Sync
use std::rc::Rc;
use std::thread;
let data = Rc::new(vec![1,2,3]);
thread::spawn(move || {
println!("{:?}", data);
});
Mejor:
use std::sync::Arc;
use std::thread;
let data = Arc::new(vec![1,2,3]);
let d = data.clone();
thread::spawn(move || {
println!("{:?}", d);
});
4) Bloquear en async o mezclar runtimes
Usar std::thread::sleep dentro de una función async o mezclar tokio/async-std sin cuidado llega a bloquear el runtime o provocar deadlocks.
// Mal: bloquea todo el executor
async fn do_work() {
std::thread::sleep(std::time::Duration::from_secs(1));
}
Mejor (tokio):
// Usa temporizadores asincrónicos
async fn do_work() {
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
Evita mezclar runtimes o bloqueos sin spawn_blocking cuando necesites ejecutar trabajo sinérgico con blocking APIs.
5) Uso inseguro de unsafe sin pruebas
unsafe permite saltarse garantías del compilador. Si no lo entiendes completamente, introduces UB. Mantén el uso de unsafe limitado, bien documentado y cubierto por tests.
// Ejemplo peligroso: dereferenciar puntero sin verificar
let ptr = 0x12345 as *const i32;
unsafe {
let _v = *ptr; // UB si la dirección no es válida
}
Mejor: encapsula unsafe en APIs seguras y documenta los invariantes que justifican el uso de unsafe. Usa crates como bound o validaciones previas y añade tests con miri cuando sea posible.
6) Mal manejo de lifetimes y referencias
Confundir &String y &str, o devolver referencias a datos que no viven lo suficiente, es error frecuente.
// Mal: devuelve referencia a dato local
fn get_str() -> &str {
let s = String::from("hola");
&s // error: s se destruye al salir
}
Mejor: devolver String o parametrizar lifetimes correctamente.
// Devolver owned
fn get_string() -> String {
String::from("hola")
}
// O parametrizar lifetimes para referencias válidas
fn first_word<'a>(s: &'a str) -> &'a str {
s.split_whitespace().next().unwrap_or("")
}
7) Malinterpretar iteradores y recolecciones (allocs innecesarias)
Recoger en Vec y luego iterar puede causar allocations y clones evitables. Aprovecha iteradores y adaptadores para streaming y composición sin asignaciones intermedias.
// Mal: collect innecesario
let v: Vec = (0..1000).collect();
let sum: i32 = v.iter().map(|x| x * 2).sum();
Mejor: componer sin almacenamiento intermedio cuando no hace falta:
// evita el vector si no es necesario
let sum: i32 = (0..1000).map(|x| x * 2).sum();
Herramientas y hábitos para evitarlos
- Clippy: cargo clippy -- -D warnings (habilita lints importantes).
- Rustfmt: formatea y evita estilo confuso.
- MIRI: detecta UB en tiempo de ejecución interpretado.
- cargo-audit: busca vulnerabilidades en dependencias.
- Tests y benches en release (cargo bench / Criterion).
Consejo avanzado: integra clippy (con pedantic si quieres) y MIRI en CI, escribe tests property-based para invariantes (proptest) y limita el uso de unsafe a módulos pequeños con documentación de invariantes. Precaución: cualquier uso de unsafe requiere revisión cuidadosa y pruebas específicas contra UB.
¿Quieres comentar?
Inicia sesión con Telegram para participar en la conversación