Pin<P> em Rust em 2026: Async, Unpin e Ergonomia

Guia pratico de Pin em Rust em 2026: por que Pin existe, Pin<Box<T>>, Pin<&mut T>, Unpin, async/await, field projections e quando se preocupar com ergonomia.

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.

O que mudou no radar de Pin em 2026

Pin continua sendo uma API de baixo nivel, mas voltou a aparecer em conversas de linguagem porque o projeto Rust esta trabalhando em ergonomia para tipos que precisam de enderecos estaveis. O ponto pratico para dev brasileiro nao e decorar todos os detalhes de RFC: e entender quando Pin faz parte da fronteira de uma biblioteca e quando ele deve ficar escondido atras de uma API simples.

Tres temas importam hoje:

  1. Ergonomia de Pin: bibliotecas async e de sistemas querem reduzir a quantidade de unsafe e boilerplate quando um tipo pinned precisa expor campos internos.
  2. Field projections: projetar um campo de uma struct pinned e dificil porque mover o campo errado quebra a garantia de endereco estavel. O desenho de linguagem busca tornar esse padrao menos manual.
  3. Rust de alto nivel: async fn em traits, macros como pin! e runtimes maduros fazem com que a maioria dos devs use Pin indiretamente, sem escrever unsafe.

Em trabalho cotidiano, trate Pin como sinal de fronteira: se voce esta usando Tokio, Axum, streams ou futures boxed, provavelmente so precisa entender o erro do compilador. Se esta escrevendo runtime, biblioteca async, wrapper FFI ou estrutura auto-referencial, precisa revisar as invariantes com muito mais cuidado.

Resumo rapido para entrevistas e codigo real

PerguntaResposta curta
Pin move o valor para outro lugar?Nao. Ele restringe como voce acessa o valor para impedir moves futuros.
Todo Pin<T> impede move?So quando o tipo interno nao implementa Unpin. Para tipos Unpin, Pin e praticamente transparente.
Box::pin sempre aloca?Sim, porque coloca o valor no heap. Use quando precisa de ownership transferivel.
pin! aloca?Nao. Ele pina um valor local na stack, util para escopos curtos.
Posso projetar campos pinned manualmente?Pode, mas normalmente isso envolve API especializada ou unsafe. Prefira crates e padroes estabelecidos.
Quando aparece em vaga Rust?Em async avancado, sistemas, networking, runtimes, FFI e bibliotecas de infraestrutura.

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