Visao Geral de Pin
Pin<P> e um wrapper que impede que um valor seja movido na memoria. Em Rust, valores podem ser movidos livremente — quando voce faz let b = a;, os bytes de a sao copiados para b e a e invalidado. Para a maioria dos tipos isso e inofensivo, mas para structs auto-referenciais (que contem ponteiros para si mesmas) um move invalidaria esses ponteiros internos.
O principal motivador para a existencia de Pin e o suporte a async/await. Futures geradas pelo compilador frequentemente contem referencias internas que apontam para outros campos da propria struct. Se essa struct fosse movida, os ponteiros internos apontariam para memoria invalida.
Pin<P> nao e magia — e um tipo que:
- Envolve um ponteiro
P(como&mut TouBox<T>) - Restringe o acesso ao
&mut Tinterno (para impedirmem::swapoumem::replace) - Garante ao tipo
Tque ele nao sera movido
A trait Unpin marca tipos que nao se importam em ser movidos, mesmo quando “pinados”. A grande maioria dos tipos e Unpin, e para eles Pin nao tem efeito pratico.
Tipos e Traits Principais
| Tipo/Trait | Descricao |
|---|---|
Pin<P> | Wrapper que garante que o valor apontado nao sera movido |
Unpin | Trait auto: tipo pode ser movido mesmo dentro de Pin |
pin! macro | Cria um Pin<&mut T> na stack (estabilizada em Rust 1.68) |
Metodos de Pin
| Metodo | Descricao |
|---|---|
Pin::new(ptr) | Cria Pin (so para tipos Unpin) |
Pin::into_inner(pin) | Extrai o ponteiro interno (so para Unpin) |
unsafe Pin::new_unchecked(ptr) | Cria Pin para tipos !Unpin (unsafe) |
.as_ref() | Converte Pin<&mut T> para Pin<&T> |
.as_mut() | Reborrow de Pin<&mut T> |
.get_mut() | Acessa &mut T (so para Unpin) |
unsafe .get_unchecked_mut() | Acessa &mut T para !Unpin (unsafe) |
Exemplos Praticos
1. Pin Basico com Tipos Unpin
Para tipos Unpin (a maioria), Pin e transparente — voce pode criar e usar livremente:
use std::pin::Pin;
fn main() {
// i32 e Unpin, entao Pin nao restringe nada
let mut valor = 42;
let pin = Pin::new(&mut valor);
// Podemos acessar o &mut normalmente via get_mut()
*pin.get_mut() = 100;
println!("Valor: {valor}"); // 100
// Pin<Box<T>> para tipos Unpin
let mut caixa = Box::pin(String::from("Ola"));
println!("Pinned string: {}", caixa.as_ref().get_ref());
// Podemos obter &mut String porque String e Unpin
caixa.as_mut().get_mut().push_str(", mundo!");
println!("Modificada: {}", caixa);
// Vec, HashMap, i32, String... todos sao Unpin
println!("i32 e Unpin? {}", std::mem::size_of::<i32>() >= 0); // sempre true
}
2. Por Que Pin Existe — O Problema de Structs Auto-Referenciais
use std::pin::Pin;
use std::marker::PhantomPinned;
// Struct auto-referencial: campo 'ptr' aponta para 'dados'
struct AutoRef {
dados: String,
ptr: *const String,
_pin: PhantomPinned, // Torna o tipo !Unpin
}
impl AutoRef {
fn new(texto: &str) -> Pin<Box<Self>> {
let mut this = Box::new(AutoRef {
dados: String::from(texto),
ptr: std::ptr::null(),
_pin: PhantomPinned,
});
// Agora apontamos ptr para dados
let ptr_dados: *const String = &this.dados;
// SAFETY: estamos inicializando o campo antes de pinar.
// Apos Pin::from(box), o valor nao sera movido.
unsafe {
let mut_ref = Pin::get_unchecked_mut(Pin::as_mut(&mut Pin::from(
// Precisamos usar um truque aqui
Box::from_raw(Box::into_raw(this))
)));
mut_ref.ptr = ptr_dados;
Pin::new_unchecked(Box::from_raw(
mut_ref as *mut AutoRef
))
}
}
fn dados_via_ptr(&self) -> &str {
// SAFETY: ptr sempre aponta para dados, que e valido
// enquanto AutoRef existir e nao for movido
unsafe { &*self.ptr }
}
}
fn main() {
let pinned = AutoRef::new("dados importantes");
println!("Direto: {}", pinned.dados);
println!("Via ptr: {}", pinned.dados_via_ptr());
// Nao podemos mover o valor para fora do Pin!
// let movido = *pinned; // ERRO: nao implementa Unpin
}
3. Pin e Async/Await — O Caso de Uso Principal
use std::pin::Pin;
use std::future::Future;
use std::task::{Context, Poll};
// Uma Future simples que conta ate um numero
struct Contador {
atual: u32,
alvo: u32,
}
impl Contador {
fn new(alvo: u32) -> Self {
Contador { atual: 0, alvo }
}
}
impl Future for Contador {
type Output = u32;
fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<u32> {
// Para tipos Unpin, podemos acessar &mut self normalmente
self.atual += 1;
if self.atual >= self.alvo {
Poll::Ready(self.atual)
} else {
// Em um executor real, registrariamos um waker
Poll::Pending
}
}
}
// Funcao que aceita qualquer Future pinado
fn tipo_da_future(_f: Pin<&dyn Future<Output = u32>>) {
println!("Recebi uma future pinada");
}
fn main() {
// Box::pin e a forma mais comum de pinar uma Future
let future = Box::pin(Contador::new(5));
tipo_da_future(future.as_ref());
// Na pratica, voce usa async/await e o runtime faz o pinning
// tokio::runtime::Runtime::new().unwrap().block_on(async {
// let resultado = minha_funcao_async().await;
// });
println!("Futures precisam ser pinadas para serem polled");
}
4. O Macro pin! para Pinning na Stack
use std::pin::pin;
use std::future::Future;
use std::task::{Context, Poll};
struct FutureSimples;
impl Future for FutureSimples {
type Output = &'static str;
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Ready("concluido!")
}
}
// pin! permite pinar na stack sem Box (zero alocacao)
fn demonstrar_pin_macro() {
// Sem o macro: precisaria de Box::pin (aloca no heap)
let _heap_pinned = Box::pin(FutureSimples);
// Com o macro: pinado na stack (zero custo)
let stack_pinned = pin!(FutureSimples);
// stack_pinned e do tipo Pin<&mut FutureSimples>
let _ref_pin: Pin<&FutureSimples> = stack_pinned.as_ref();
println!("pin! macro evita alocacao heap");
}
// pin! e especialmente util com futures em funcoes sync
fn executar_future_simples() {
let mut future = pin!(async {
let a = 1;
let b = 2;
a + b
});
// Para demonstracao: em codigo real, use um executor async
let _pinned_ref: Pin<&mut _> = future.as_mut();
println!("Future pinada na stack com sucesso");
}
fn main() {
demonstrar_pin_macro();
executar_future_simples();
}
5. Padroes com Pin em APIs Async
use std::pin::Pin;
use std::future::Future;
// Tipo alias comum para futures boxed (trait objects)
type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
// Funcao que retorna uma future boxed (util para trait methods)
fn buscar_dados(url: String) -> BoxFuture<'static, Result<String, String>> {
Box::pin(async move {
// Simula uma operacao assincrona
if url.starts_with("http") {
Ok(format!("Dados de {url}"))
} else {
Err(format!("URL invalida: {url}"))
}
})
}
// Trait com metodo async (padrao pre-async-in-traits)
trait Servico {
fn processar(&self, entrada: String) -> BoxFuture<'_, String>;
}
struct MeuServico;
impl Servico for MeuServico {
fn processar(&self, entrada: String) -> BoxFuture<'_, String> {
Box::pin(async move {
format!("Processado: {entrada}")
})
}
}
// Com async fn em traits (Rust 1.75+), Pin e menos necessario:
// trait ServicoModerno {
// async fn processar(&self, entrada: String) -> String;
// }
fn main() {
let _future = buscar_dados("https://exemplo.com".into());
let servico = MeuServico;
let _resultado = servico.processar("dados".into());
println!("BoxFuture = Pin<Box<dyn Future<...>>>");
println!("E o padrao para futures como trait objects");
}
Padroes Comuns
Quando Voce Encontra Pin
Na pratica, voce encontra Pin em tres contextos:
- Implementando
Futuremanualmente — o metodopollrecebeself: Pin<&mut Self> - Retornando futures como trait objects —
Pin<Box<dyn Future<Output = T>>> - Usando APIs de streaming —
Stream::poll_nexttambem recebePin<&mut Self>
Regra Pratica de Unpin
Se seu tipo:
- Contem apenas tipos primitivos,
String,Vec,Box, etc. -> eUnpinautomaticamente - Contem
PhantomPinned-> e!Unpin - E uma future gerada por
async-> e!Unpin
Pin<Box> vs pin! macro
| Abordagem | Alocacao | Quando usar |
|---|---|---|
Box::pin(valor) | Heap | Quando precisa de ownership transferivel |
pin!(valor) | Stack | Quando o pinned value e local |
Quando Usar (e Quando Nao Usar)
Use Pin quando:
- Implementar a trait
Futuremanualmente - Retornar futures como trait objects (
BoxFuture) - Criar tipos auto-referenciais (raro, mas necessario)
- Trabalhar com APIs que requerem
Pin(comotokio::select!)
Nao use Pin quando:
- Todos os seus tipos forem
Unpin(a maioria e!) - Puder usar
async/awaitnormalmente sem implementarFuturemanualmente - Puder usar
async fnem traits (Rust 1.75+) - Nao estiver trabalhando com futures ou structs auto-referenciais
Dica: Em codigo async cotidiano, voce raramente precisa interagir com Pin diretamente. O compilador e os runtimes (tokio, async-std) cuidam do pinning. Voce so precisa entender Pin quando estiver escrevendo abstractions de baixo nivel.
Performance: Pin tem custo zero — e apenas um wrapper que restringe a API em tempo de compilacao. Nao adiciona nenhum overhead em runtime.
Veja Tambem
- Async/Await em Profundidade — como Pin se integra ao sistema async
- Modulo std::marker — trait
UnpinePhantomPinned - Modulo std::mem —
swapereplaceque Pin restringe - Modulo std::ptr — ponteiros raw usados em structs auto-referenciais
- Documentacao oficial — std::pin