O trait Future

1. O que é um Future?

Em Rust, um Future representa uma computação assíncrona que pode ou não ter sido concluída ainda. Pense nele como uma promessa: você recebe um ticket que, em algum momento no futuro, lhe entregará um valor. Enquanto isso, seu programa pode continuar fazendo outras tarefas sem ficar bloqueado esperando.

A analogia mais simples é um pedido em uma cafeteria: você faz o pedido (cria o Future), recebe uma senha (o próprio Future) e continua lendo um livro enquanto o café não fica pronto. Quando o café está pronto, você é notificado (o Future é resolvido) e pode consumir o resultado.

use std::future::Future;

// Um Future que promete retornar um inteiro
async fn um_numero() -> i32 {
    42
}

// O código acima é equivalente a:
fn um_numero_future() -> impl Future<Output = i32> {
    async { 42 }
}

2. Anatomia do trait Future

O trait Future é surpreendentemente simples em sua definição:

use std::pin::Pin;
use std::task::{Context, Poll};

pub trait Future {
    type Output;

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}
  • Output: O tipo do valor que o Future produzirá quando completo
  • poll: O método central que tenta avançar o estado do Future. É chamado repetidamente pelo executor até que o Future seja resolvido

3. O papel do Pin e do Context

Por que Pin?

Pin garante que o dado em memória não seja movido enquanto o Future está sendo pollado. Isso é crucial para Futures que contêm auto-referências, comuns em máquinas de estado geradas por async/await.

// Exemplo conceitual de um Future auto-referencial
struct SelfReferential {
    data: String,
    ptr: *const String, // Aponta para self.data
}

// Se este struct for movido, ptr se torna inválido!
// Pin impede esse movimento durante o polling

O que é Context?

Context fornece acesso ao Waker, o mecanismo que permite ao Future notificar o executor que ele está pronto para ser pollado novamente:

use std::task::Waker;

fn exemplo_waker(cx: &mut Context<'_>) {
    let waker = cx.waker().clone();

    // Em algum momento futuro, quando os dados estiverem prontos:
    waker.wake(); // Notifica o executor
}

4. Estados de um Future

Um Future pode estar em dois estados:

use std::task::Poll;

enum Poll<T> {
    Ready(T),  // O Future foi concluído com o valor T
    Pending,   // O Future ainda não está pronto
}

Exemplo prático: um Future que espera um tempo antes de completar:

use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::{Duration, Instant};

struct Delay {
    when: Instant,
}

impl Future for Delay {
    type Output = &'static str;

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        if Instant::now() >= self.when {
            Poll::Ready("Tempo esgotado!")
        } else {
            // Agenda para ser pollado novamente quando o tempo chegar
            let waker = cx.waker().clone();
            let when = self.when;
            std::thread::spawn(move || {
                let now = Instant::now();
                if now < when {
                    std::thread::sleep(when - now);
                }
                waker.wake();
            });
            Poll::Pending
        }
    }
}

5. Combinadores de Futures

Combinadores permitem encadear e combinar múltiplos Futures de forma elegante:

use futures::future::{join, select, FutureExt};

async fn tarefa_1() -> u32 { 10 }
async fn tarefa_2() -> u32 { 20 }

async fn exemplo_combinadores() {
    // join: espera ambos completarem
    let (a, b) = join(tarefa_1(), tarefa_2()).await;
    println!("Soma: {}", a + b);

    // map: transforma o resultado
    let resultado = tarefa_1().await;
    let dobrado = resultado * 2;
    println!("Dobrado: {}", dobrado);

    // select: espera o primeiro completar
    let primeiro = select(tarefa_1(), tarefa_2()).await;
    println!("Primeiro a completar: {}", primeiro.factor_first().0);
}

6. Implementando um Future personalizado

Vamos criar um Future que conta regressivamente de 5 a 0:

use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};

struct ContagemRegressiva {
    contador: u32,
}

impl ContagemRegressiva {
    fn new(inicio: u32) -> Self {
        ContagemRegressiva { contador: inicio }
    }
}

impl Future for ContagemRegressiva {
    type Output = u32;

    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        if self.contador == 0 {
            println!("Contagem concluída!");
            Poll::Ready(0)
        } else {
            println!("Contagem: {}", self.contador);
            self.contador -= 1;

            // Agenda o próximo polling
            let waker = cx.waker().clone();
            std::thread::spawn(move || {
                std::thread::sleep(std::time::Duration::from_secs(1));
                waker.wake();
            });

            Poll::Pending
        }
    }
}

// Uso com um executor simples
fn main() {
    let mut future = ContagemRegressiva::new(5);
    let waker = futures::task::noop_waker();
    let mut cx = Context::from_waker(&waker);

    loop {
        match Pin::new(&mut future).poll(&mut cx) {
            Poll::Ready(valor) => {
                println!("Valor final: {}", valor);
                break;
            }
            Poll::Pending => {
                std::thread::sleep(std::time::Duration::from_millis(100));
            }
        }
    }
}

7. Relação com Async/Await e Executores

A mágica do async/await é que o compilador transforma sua função em uma máquina de estados que implementa Future:

// Esta função async é transformada pelo compilador em um Future
async fn buscar_dados() -> String {
    // Simula uma operação assíncrona
    "dados importantes".to_string()
}

// Equivalente aproximado do que o compilador gera:
fn buscar_dados_future() -> impl Future<Output = String> {
    // Uma máquina de estados complexa com auto-referências
    async { "dados importantes".to_string() }
}

// Executando com Tokio
#[tokio::main]
async fn main() {
    let dados = buscar_dados().await;
    println!("{}", dados);
}

Execução Lazy vs Eager

Futures em Rust são lazy: eles não fazem nada até serem pollados. Isso difere de outras linguagens onde promessas começam a executar imediatamente:

let futuro = async {
    println!("Isso só será executado quando pollado");
    42
};

// Nada foi impresso ainda
println!("Future criado, mas não executado");

// Agora sim, o executor polla o Future
futuro.await;

8. Boas práticas e armadilhas comuns

Evitar bloqueios em poll

Nunca use thread::sleep ou outras operações bloqueantes dentro de poll:

// ERRADO: bloqueia o executor
impl Future for MeuFuture {
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        std::thread::sleep(Duration::from_secs(1)); // NUNCA FAÇA ISSO
        Poll::Ready(())
    }
}

// CORRETO: usa temporizador assíncrono
impl Future for MeuFuture {
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        // Usa tokio::time::sleep ou similar
        Poll::Pending
    }
}

Gerenciamento de Pin

use std::pin::Pin;

// Box::pin - para heap allocation (recomendado para casos gerais)
let future = Box::pin(async { 42 });

// Pin::new - apenas para tipos que já são !Unpin
let mut valor = 42;
let pinned = Pin::new(&mut valor);

// pin_mut! - para pinagem na stack (do crate futures)
use futures::pin_mut;
let futuro = async { 42 };
pin_mut!(futuro);

Testando Futures

use futures::executor::block_on;

#[test]
fn test_future() {
    let resultado = block_on(async {
        let mut contador = ContagemRegressiva::new(3);
        (&mut contador).await
    });
    assert_eq!(resultado, 0);
}

Referências