Pin e Unpin: entendendo self-referential structs
1. O problema fundamental: por que structs auto-referenciais são perigosas?
Em Rust, a segurança de memória depende de uma regra simples: mover um valor invalida todas as referências para ele. Quando uma struct contém um ponteiro para um de seus próprios campos, mover essa struct quebra esse ponteiro internamente, criando um dangling pointer — comportamento indefinido (UB).
struct SelfReferential {
data: String,
ptr: *const String, // ponteiro cru para self.data
}
impl SelfReferential {
fn new(data: String) -> Self {
let mut s = SelfReferential { ptr: std::ptr::null(), data };
s.ptr = &s.data as *const String;
s
}
}
fn main() {
let mut original = SelfReferential::new("hello".to_string());
let moved = original; // original é movido, ptr agora aponta para endereço inválido
// println!("{:?}", unsafe { &*original.ptr }); // UB!
}
O problema: quando original é movido para moved, o endereço de data muda, mas ptr ainda aponta para o local antigo. Rust não pode verificar isso estaticamente.
2. Pin: garantindo que um valor não seja movido
Pin<P> é um wrapper que garante que o valor apontado por P nunca será movido. Se um valor está "pinado", você não pode mais movê-lo — a única forma de acessá-lo é através do próprio Pin.
use std::pin::Pin;
fn main() {
let mut data = String::from("hello");
let mut pinned = Pin::new(&mut data); // pino no stack
// Pin::new(&mut data) só funciona porque String é Unpin
// Não podemos mover data enquanto pinned existir
}
Pin<Box<T>> aloca no heap e pina o valor, enquanto Pin<&mut T> pina uma referência. Com Deref, você pode ler imutavelmente; com DerefMut, o acesso mutável é restrito para tipos !Unpin.
3. Unpin: a exceção que confirma a regra
Unpin é um trait auto-implementado para a maioria dos tipos. Tipos Unpin podem ser movidos mesmo quando pinados — Pin::new funciona sem unsafe. Tipos como i32, String, Vec<T> são Unpin.
fn move_unpin<T: Unpin>(pinned: Pin<&mut T>) {
let mut t = *pinned; // move para fora do Pin — permitido porque T: Unpin
}
Tipos !Unpin (marcados com PhantomPinned) não podem ser movidos. O compilador impede operações como mem::replace ou atribuição direta.
4. Construindo uma struct auto-referencial segura com Pin
Para criar uma struct auto-referencial segura, marcamos o tipo como !Unpin usando PhantomPinned e inicializamos em duas etapas:
use std::marker::PhantomPinned;
use std::pin::Pin;
use std::ptr::NonNull;
struct SelfRef {
data: String,
self_ptr: Option<NonNull<String>>,
_pin: PhantomPinned, // torna o tipo !Unpin
}
impl SelfRef {
fn new(data: String) -> Self {
SelfRef {
data,
self_ptr: None,
_pin: PhantomPinned,
}
}
fn init(self: Pin<&mut Self>) {
let this = unsafe { self.get_unchecked_mut() };
this.self_ptr = Some(NonNull::from(&this.data));
}
fn get_data(self: Pin<&Self>) -> &String {
unsafe { self.self_ptr.unwrap().as_ref() }
}
}
fn main() {
let mut sr = SelfRef::new("seguro".to_string());
let pinned = unsafe { Pin::new_unchecked(&mut sr) };
pinned.init();
println!("{}", pinned.get_data());
// sr não pode mais ser movido diretamente
}
Aqui, Pin::new_unchecked é unsafe porque confiamos que sr nunca será movido enquanto pinned existir.
5. Pin e o mundo assíncrono: o coração das Futures
O trait Future::poll recebe self: Pin<&mut Self> justamente porque async blocks podem gerar tipos auto-referenciais. Quando você escreve:
async fn exemplo() {
let x = 42;
let y = &x; // referência a uma variável local
some_future.await;
// y ainda é válido — o compilador gera uma struct auto-referencial
}
O compilador gera um tipo !Unpin automaticamente. Sem Pin, mover essa Future invalidaria a referência interna.
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
struct MinhaFuture {
valor: i32,
ref_interna: *const i32,
_pin: PhantomPinned,
}
impl Future for MinhaFuture {
type Output = i32;
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = unsafe { self.get_unchecked_mut() };
this.ref_interna = &this.valor as *const i32;
Poll::Ready(unsafe { *this.ref_interna })
}
}
6. Armadilhas comuns e boas práticas
Pin::new_unchecked: só use quando você tem garantia absoluta de que o valor não será movido. É unsafe por uma razão.
Não confunda Pin<Box<T>> com Box<Pin<T>>: o primeiro pina o valor dentro da Box; o segundo coloca um Pin dentro de uma Box (inútil).
Pin projections: para acessar campos de uma struct pinada, você precisa de projeções seguras. A crate pin-project ajuda:
use pin_project::pin_project;
#[pin_project]
struct Projetada {
#[pin]
pin_campo: String,
normal_campo: i32,
}
impl Projetada {
fn use_pin(self: Pin<&mut Self>) {
let this = self.project();
let _pin_ref = this.pin_campo; // Pin<&mut String>
let _normal_ref = this.normal_campo; // &mut i32
}
}
Erro comum: tentar usar mem::replace ou take em um valor pinado — o compilador impede.
7. Pin na prática: padrões de API e bibliotecas
A macro pin! do futures crate permite pinar no stack sem alocação:
use futures::pin_mut;
async fn processar() {
let fut = async { 42 };
pin_mut!(fut); // fut agora é Pin<&mut impl Future>
fut.await;
}
Para tokio::spawn, você precisa de Pin<Box<dyn Future + Send + 'static>>:
use tokio;
#[tokio::main]
async fn main() {
let fut = async {
let data = String::from("exemplo");
let ref_data = &data;
println!("{}", ref_data);
};
tokio::spawn(fut).await.unwrap(); // fut é movido para o runtime
}
A diferença entre Pin<Box<dyn Future>> (alocação dinâmica) e Pin<&mut dyn Future> (referência) impacta performance e flexibilidade. Prefira Pin<&mut dyn Future> quando possível.
Referências
- The Rust Reference: Pin — Documentação oficial sobre o tipo
Pine suas garantias. - std::pin module documentation — Documentação completa da API de
Pin,UnpinePhantomPinned. - The Rust Async Book: Pinning — Capítulo dedicado a Pin no contexto de programação assíncrona.
- Rust RFC 2349: Pin — RFC original que introduziu
Pinna linguagem, com motivações e design. - futures-rs crate documentation — Documentação da macro
pin_mut!e outras ferramentas para trabalhar com Pin. - pin-project crate — Biblioteca para criar projeções seguras em structs pinadas.
- Jon Gjengset's "Pin and Suffering" — Palestra técnica aprofundada sobre Pin, Unpin e self-referential structs.