Principales errores de desarrolladores principiantes de Rust: nivel avanzado

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

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; }}

Blogs

Book Recommendations