Principales errores de desarrolladores principiantes de Rust: nivel avanzado
Rust es un lenguaje que promete seguridad en la gestión de memoria y alto rendimiento. Sin embargo, cuando pasas de ejemplos básicos a proyectos reales, empiezan a surgir errores que no se resuelven simplemente añadiendo & o mut. En este artículo, analizaremos cuatro trampas avanzadas en las que suelen caer los desarrolladores que ya dominan los fundamentos del borrow checker y la propiedad.
El artículo está dirigido a quienes ya han escrito varios proyectos en Rust y se han enfrentado a Rc, RefCell, traits y unsafe. No explicaremos qué es el préstamo, sino que nos centraremos en los errores ocultos.
1. Fugas de memoria por referencias cíclicas en Rc/RefCell
El error más engañoso es pensar que Rc previene automáticamente las fugas de memoria. En realidad, Rc usa conteo de referencias, y si dos objetos se referencian mutuamente, el contador nunca llega a cero. La memoria no se libera.
Consideremos un ejemplo clásico con un grafo:
use std::rc::Rc;use std::cell::RefCell;
#[derive(Debug)]struct Node { value: i32, children: Vec<Rc<RefCell<Node>>>,}
fn main() { let a = Rc::new(RefCell::new(Node { value: 1, children: vec![] })); let b = Rc::new(RefCell::new(Node { value: 2, children: vec![] }));
// Creamos una referencia cíclica a.borrow_mut().children.push(Rc::clone(&b)); b.borrow_mut().children.push(Rc::clone(&a));
// ¡Al salir de main la memoria no se libera! drop(a); drop(b); // Fuga: Rc cree que todavía hay referencias}¿Por qué ocurre esto? Después de drop(a), el contador de referencias de a disminuye, pero b aún contiene una referencia a a a través de children. Lo mismo ocurre con b. Como resultado, ambos objetos permanecen en memoria.
Solución: Usa Weak<T> para referencias débiles. En los grafos, normalmente un nodo es el propietario (referencia fuerte) y los demás son débiles.
use std::rc::{Rc, Weak};use std::cell::RefCell;
#[derive(Debug)]struct Node { value: i32, parent: RefCell<Weak<Node>>, // referencia débil al padre children: RefCell<Vec<Rc<Node>>>, // referencias fuertes a los hijos}
fn main() { let leaf = Rc::new(Node { value: 3, parent: RefCell::new(Weak::new()), children: RefCell::new(vec![]), });
let branch = Rc::new(Node { value: 5, parent: RefCell::new(Weak::new()), children: RefCell::new(vec![Rc::clone(&leaf)]), });
*leaf.parent.borrow_mut() = Rc::downgrade(&branch); // Ahora al eliminar branch, leaf también se liberará}2. Uso incorrecto de Pin para estructuras auto-referenciales
Las estructuras que contienen referencias a sí mismas (auto-referenciales) son una causa común de comportamiento indefinido. Rust no permite crearlas directamente, pero a través de Pin y unsafe es posible. El error es olvidar que Pin solo garantiza que el objeto no será movido en memoria, pero no protege contra desplazamientos internos.
Ejemplo con un future:
use std::pin::Pin;use std::marker::PhantomPinned;
#[derive(Debug)]struct SelfReferential { data: String, pointer: *const String, // puntero crudo a data _pin: PhantomPinned,}
impl SelfReferential { fn new(data: String) -> Self { SelfReferential { pointer: std::ptr::null(), data, _pin: PhantomPinned, } }
fn init(self: Pin<&mut Self>) { let this = unsafe { self.get_unchecked_mut() }; this.pointer = &this.data as *const String; }}