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 completopoll: 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
- The Rust Async Book: Futures — Documentação oficial explicando o conceito de Futures em Rust
- Rust Reference: The Future Trait — Documentação oficial do trait Future na std
- Tokio Tutorial: Futures — Tutorial prático do Tokio sobre programação assíncrona com Futures
- Rust by Example: Futures — Exemplos práticos de implementação do trait Future
- Async Fundamentals: Understanding Futures — Palestra do RustConf sobre os fundamentos dos Futures em Rust
- Futures-rs Crate Documentation — Documentação do crate futures com combinadores e utilitários
- Pin, Unpin, and why Rust needs them — Artigo do Cloudflare explicando a necessidade do Pin em Futures auto-referenciais