Extending FutureForm #
The FutureForm trait is open for extension. This document sketches out possible implementations beyond the built-in Sendable and Local.
The Constraint #
pub trait FutureForm {
type Future<'a, T: 'a>: Future<Output = T> + 'a;
}
Any implementation must provide a Future type that:
- Works for any output type
T - Works for any lifetime
'a - Implements
Future<Output = T>
This rules out concrete async block types (which have fixed output types), but allows any generic future wrapper.
Traced Futures (Hypothetical) #
A FutureForm that logs every poll — useful for debugging or instrumentation:
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use futures::future::BoxFuture;
pub struct Traced;
pub struct TracedFuture<'a, T> {
inner: BoxFuture<'a, T>,
name: &'static str,
}
impl<'a, T> Future for TracedFuture<'a, T> {
type Output = T;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T> {
let this = self.as_mut().get_mut();
eprintln!("[trace] polling {}", this.name);
Pin::new(&mut this.inner).poll(cx)
}
}
impl FutureForm for Traced {
type Future<'a, T: 'a> = TracedFuture<'a, T>;
}
This pattern extends to metrics collection, cancellation tokens, or any wrapper that needs to observe or modify future behavior.
Result-Wrapped Futures #
For operations that might fail at construction time:
use std::future::{Future, Ready, ready};
use std::pin::Pin;
use std::task::{Context, Poll};
use futures::future::BoxFuture;
/// Futures that capture a construction-time error.
pub struct Fallible;
pub enum FallibleFuture<'a, T> {
Ok(BoxFuture<'a, T>),
Err(Ready<T>), // For error cases with default/error value
}
impl<'a, T> Future for FallibleFuture<'a, T> {
type Output = T;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T> {
match self.get_mut() {
Self::Ok(f) => Pin::new(f).poll(cx),
Self::Err(f) => Pin::new(f).poll(cx),
}
}
}
impl FutureForm for Fallible {
type Future<'a, T: 'a> = FallibleFuture<'a, T>;
}
Static Dispatch (Limited) #
True static dispatch without boxing is possible but limited. The future type must be nameable and generic:
use std::future::Ready;
/// For operations that complete immediately.
pub struct Immediate;
impl FutureForm for Immediate {
type Future<'a, T: 'a> = Ready<T>;
}
Usage:
impl MyTrait<Immediate> for MyType {
fn get_value(&self) -> Ready<u32> {
std::future::ready(42)
}
}
This works for Ready, Pending, or any nameable future type, but not for async blocks (whose types are anonymous and unnameable).
Why Not Fully Unboxed? #
You might want:
impl FutureForm for Unboxed {
type Future<'a, T: 'a> = impl Future<Output = T>; // Not valid
}
This doesn't work because:
- Associated types can't use
impl Trait - Async blocks have anonymous, unnameable types
- Each async block creates a different type, but
FutureForm::Futuremust be a single type
The boxing in Sendable/Local is precisely what enables type erasure across different async implementations.
The FromFuture Trait #
The crate provides a FromFuture trait that abstracts over lifting a future into a kind's future type:
pub trait FromFuture<'a, T, F>
where
T: 'a,
F: Future<Output = T> + 'a,
{
fn from_future(f: F) -> Self;
}
This is implemented for BoxFuture (requiring F: Send) and LocalBoxFuture (no Send requirement). Custom future types can implement this trait to enable uniform construction.
Usage is uniform across all forms via FutureForm::from_future:
impl Counter<Sendable> for Memory {
fn next(&self) -> BoxFuture<'_, u32> {
Sendable::from_future(async { self.val + 1 })
}
}
impl Counter<Local> for Memory {
fn next(&self) -> LocalBoxFuture<'_, u32> {
Local::from_future(async { self.val + 1 })
}
}
Using Custom Kinds with #[future_form] #
The #[future_form] macro supports custom FutureForm types alongside the built-in Sendable and Local:
#[future_form(Sendable, Local, Traced)]
impl<K: FutureForm> MyTrait<K> for MyType<K> {
fn operation(&self) -> K::Future<'_, u32> {
K::from_future(async { self.value })
}
}
For custom types, the macro replaces K::Future<'a, T> with CustomType::Future<'a, T> (the associated type) rather than a concrete type like BoxFuture.
Note: Custom types must implement
FromFuturefor thefrom_futurepattern to work. Types likeImmediatethat use non-boxed futures (e.g.,Ready<T>) require different construction patterns.