Principais erros de desenvolvedores Rust iniciantes: nível avançado
Rust — uma linguagem que promete segurança de memória e alta performance. No entanto, quando você passa de exemplos básicos para projetos reais, começam a surgir erros que não são resolvidos simplesmente adicionando & ou mut. Neste artigo, analisaremos quatro armadilhas avançadas nas quais desenvolvedores que já dominam os fundamentos do borrow checker e da propriedade frequentemente caem.
O artigo é voltado para aqueles que já escreveram vários projetos em Rust e já lidaram com Rc, RefCell, traits e unsafe. Não vamos explicar o que é borrowing, mas sim focar nas armadilhas ocultas.
1. Vazamentos de memória através de referências cíclicas em Rc/RefCell
O erro mais traiçoeiro é pensar que Rc previne automaticamente vazamentos. Na verdade, Rc usa contagem de referências, e se dois objetos se referenciam mutuamente, o contador nunca será zerado. A memória não será liberada.
Vamos considerar um exemplo clássico com um 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![] }));
// Criando uma referência cíclica a.borrow_mut().children.push(Rc::clone(&b)); b.borrow_mut().children.push(Rc::clone(&a));
// Após sair do main, a memória não será liberada! drop(a); drop(b); // Vazamento: Rc acredita que ainda existem referências}Por que isso acontece? Após drop(a), o contador de referências de a diminui, mas b ainda contém uma referência a a através de children. O mesmo vale para b. No final, ambos os objetos permanecem na memória.
Solução: Use Weak<T> para referências fracas. Em grafos, geralmente um nó é o proprietário (referência forte), e os outros são fracos.
use std::rc::{Rc, Weak};use std::cell::RefCell;
#[derive(Debug)]struct Node { value: i32, parent: RefCell<Weak<Node>>, // referência fraca ao pai children: RefCell<Vec<Rc<Node>>>, // referências fortes aos filhos}
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); // Agora, ao remover branch, leaf também será liberado}2. Uso incorreto de Pin para estruturas auto-referenciais
Estruturas que contêm referências a si mesmas (self-referential) são uma causa comum de comportamento indefinido. Rust não permite criá-las diretamente, mas através de Pin e unsafe isso é possível. O erro é esquecer que Pin garante apenas que o objeto não será movido na memória, mas não protege contra deslocamentos internos.
Exemplo com uma future:
use std::pin::Pin;use std::marker::PhantomPinned;
#[derive(Debug)]struct SelfReferential { data: String, pointer: *const String, // ponteiro bruto para 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; }}