Pin<P> em Rust: Fixando Valores na Memoria

Guia completo de Pin em Rust: por que Pin existe, Pin<Box<T>>, Pin<&mut T>, Unpin, structs auto-referenciais, async/await e o macro pin!.

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:

  1. Envolve um ponteiro P (como &mut T ou Box<T>)
  2. Restringe o acesso ao &mut T interno (para impedir mem::swap ou mem::replace)
  3. Garante ao tipo T que 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/TraitDescricao
Pin<P>Wrapper que garante que o valor apontado nao sera movido
UnpinTrait auto: tipo pode ser movido mesmo dentro de Pin
pin! macroCria um Pin<&mut T> na stack (estabilizada em Rust 1.68)

Metodos de Pin

MetodoDescricao
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:

  1. Implementando Future manualmente — o metodo poll recebe self: Pin<&mut Self>
  2. Retornando futures como trait objectsPin<Box<dyn Future<Output = T>>>
  3. Usando APIs de streamingStream::poll_next tambem recebe Pin<&mut Self>

Regra Pratica de Unpin

Se seu tipo:

  • Contem apenas tipos primitivos, String, Vec, Box, etc. -> e Unpin automaticamente
  • Contem PhantomPinned -> e !Unpin
  • E uma future gerada por async -> e !Unpin

Pin<Box> vs pin! macro

AbordagemAlocacaoQuando usar
Box::pin(valor)HeapQuando precisa de ownership transferivel
pin!(valor)StackQuando o pinned value e local

Quando Usar (e Quando Nao Usar)

Use Pin quando:

  • Implementar a trait Future manualmente
  • Retornar futures como trait objects (BoxFuture)
  • Criar tipos auto-referenciais (raro, mas necessario)
  • Trabalhar com APIs que requerem Pin (como tokio::select!)

Nao use Pin quando:

  • Todos os seus tipos forem Unpin (a maioria e!)
  • Puder usar async/await normalmente sem implementar Future manualmente
  • Puder usar async fn em 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