Loading pageā¦
Rust walkthroughs
Loading pageā¦
futures::future::BoxFuture differ from Pin<Box<dyn Future>> and when would you use each?BoxFuture<'a, T> is a type alias for Pin<Box<dyn Future<Output = T> + Send + 'a>> that provides ergonomic shorthand for the common pattern of boxing futures with Send bounds. The key difference is convenience: BoxFuture bundles the common requirements (Send bound and lifetime) into a single type, while Pin<Box<dyn Future>> is the raw form that you must configure manually. Use BoxFuture when you need a Send-able boxed future (the common case in async Rust), and use Pin<Box<dyn Future>> when you need different bounds (e.g., non-Send futures, custom traits, or specific lifetime constraints). The underlying mechanism is identicalāBoxFuture is just a type alias that reduces boilerplate for the most common use case.
use std::future::Future;
use std::pin::Pin;
// The raw form requires explicit lifetime and bounds
type RawBoxedFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
// Without Send bound - cannot be sent between threads
async fn example_raw() -> RawBoxedFuture<'static, String> {
Box::pin(async {
String::from("Hello")
})
}Pin<Box<dyn Future>> requires explicit specification of lifetime and trait bounds.
use futures::future::BoxFuture;
// BoxFuture bundles Send + lifetime
// BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>
async fn example_boxfuture() -> BoxFuture<'static, String> {
Box::pin(async {
String::from("Hello")
})
}BoxFuture is a type alias with Send bound built in.
// The actual definition in futures crate:
// pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
// This means:
// 1. The future is pinned in a box
// 2. The future implements Send (can be sent between threads)
// 3. The future has lifetime 'a (typically 'static for owned futures)
// 4. The output type is TBoxFuture combines Send bound and lifetime into one type alias.
use std::future::Future;
use std::pin::Pin;
// BoxFuture requires Send - for thread-safe contexts
fn requires_send<T: Send>(_: &T) {}
fn test_boxfuture_send() {
let fut: futures::future::BoxFuture<'static, ()> = Box::pin(async {});
requires_send(&fut); // BoxFuture implements Send
}
// Pin<Box<dyn Future>> without Send - for single-threaded contexts
fn test_non_send() {
// This doesn't implement Send
let fut: Pin<Box<dyn Future<Output = ()>>> = Box::pin(async {});
// requires_send(&fut); // ERROR: doesn't implement Send
}BoxFuture always implements Send; raw Pin<Box<dyn Future>> may not.
use futures::future::LocalBoxFuture;
// LocalBoxFuture omits the Send bound
// pub type LocalBoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
// Use when your future cannot be Send
fn non_send_future() -> LocalBoxFuture<'static, String> {
// Rc is not Send, but works with LocalBoxFuture
let rc = std::rc::Rc::new("data");
Box::pin(async move {
format!("Got: {}", rc)
})
}
// This won't work with BoxFuture:
// fn try_boxfuture() -> BoxFuture<'static, String> {
// let rc = std::rc::Rc::new("data"); // Rc is not Send
// Box::pin(async move { format!("{}", rc) }) // ERROR: future not Send
// }Use LocalBoxFuture when your future contains non-Send types.
use futures::future::BoxFuture;
use std::future::Future;
use std::pin::Pin;
// 'static lifetime for owned futures
fn static_future() -> BoxFuture<'static, String> {
Box::pin(async {
String::from("owned")
})
}
// Shorter lifetime for borrowed futures
fn borrowed_future<'a>(data: &'a str) -> BoxFuture<'a, String> {
Box::pin(async move {
format!("Processed: {}", data)
})
}
// Without BoxFuture alias:
fn borrowed_raw<'a>(data: &'a str) -> Pin<Box<dyn Future<Output = String> + Send + 'a>> {
Box::pin(async move {
format!("Processed: {}", data)
})
}Both forms support lifetime parameters; BoxFuture is just shorter.
use std::future::Future;
use std::pin::Pin;
// BoxFuture for trait objects
fn trait_object_boxfuture() -> futures::future::BoxFuture<'static, i32> {
// Can return different future implementations
let condition = true;
if condition {
Box::pin(async { 1 })
} else {
Box::pin(async { 2 })
}
}
// Same with raw type
fn trait_object_raw() -> Pin<Box<dyn Future<Output = i32> + Send>> {
let condition = true;
if condition {
Box::pin(async { 1 })
} else {
Box::pin(async { 2 })
}
}Both enable returning different future types from the same function.
use futures::future::BoxFuture;
// Recursive async requires boxing (futures are infinite size)
fn recursive_fib(n: u32) -> BoxFuture<'static, u64> {
Box::pin(async move {
if n <= 1 {
n as u64
} else {
recursive_fib(n - 1).await + recursive_fib(n - 2).await
}
})
}
// Same with raw type
fn recursive_fib_raw(n: u32) -> std::pin::Pin<Box<dyn std::future::Future<Output = u64> + Send>> {
Box::pin(async move {
if n <= 1 {
n as u64
} else {
recursive_fib_raw(n - 1).await + recursive_fib_raw(n - 2).await
}
})
}Boxing enables recursive async; BoxFuture reduces verbosity.
use std::future::Future;
use std::pin::Pin;
// Collection of different futures
fn collection_of_futures() -> Vec<Pin<Box<dyn Future<Output = i32> + Send>>> {
vec![
Box::pin(async { 1 }),
Box::pin(async { 2 }),
Box::pin(async { 3 }),
]
}
// Same with BoxFuture alias
fn collection_boxfuture() -> Vec<futures::future::BoxFuture<'static, i32>> {
vec![
Box::pin(async { 1 }),
Box::pin(async { 2 }),
Box::pin(async { 3 }),
]
}
// Use LocalBoxFuture for non-Send collection
fn collection_local() -> Vec<futures::future::LocalBoxFuture<'static, i32>> {
vec![
Box::pin(async { 1 }),
Box::pin(async { 2 }),
]
}Both enable heterogeneous future collections.
use futures::future::BoxFuture;
use std::future::Future;
use std::pin::Pin;
// Generic over boxed future
async fn run_boxed(fut: BoxFuture<'static, String>) -> String {
fut.await
}
// Generic over any future
async fn run_generic<F>(fut: F) -> String
where
F: Future<Output = String>,
{
fut.await
}
// Generic over boxed future with lifetime
fn run_with_lifetime<'a>(fut: BoxFuture<'a, String>) -> BoxFuture<'a, String> {
Box::pin(async move {
fut.await
})
}BoxFuture can be used as a generic bound in functions.
use futures::future::BoxFuture;
// Use BoxFuture when:
// 1. You need to box a future
// 2. The future needs to be Send
// 3. You're returning from a trait method
// 4. You're building a recursive async function
// Example: Trait method
trait AsyncProcessor {
fn process(&self, input: String) -> BoxFuture<'static, String>;
}
struct UpperProcessor;
impl AsyncProcessor for UpperProcessor {
fn process(&self, input: String) -> BoxFuture<'static, String> {
Box::pin(async move {
input.to_uppercase()
})
}
}
// Example: Multiple return types
fn conditional_future(flag: bool) -> BoxFuture<'static, &'static str> {
if flag {
Box::pin(async { "yes" })
} else {
Box::pin(async { "no" })
}
}BoxFuture is ideal for trait methods and conditional returns.
use std::future::Future;
use std::pin::Pin;
// Use raw type when:
// 1. You need non-Send futures
// 2. You need custom trait bounds
// 3. You're learning how async works
// 4. You need Sync but not Send
// Example: Non-Send future
fn non_send_future() -> Pin<Box<dyn Future<Output = String>>> {
let rc = std::rc::Rc::new("data");
Box::pin(async move {
format!("{}", rc)
})
}
// Example: Custom trait bounds
fn custom_bounds() -> Pin<Box<dyn Future<Output = String> + Sync>> {
Box::pin(async {
String::from("sync")
})
}Use the raw type when you need different bounds.
use futures::future::LocalBoxFuture;
// LocalBoxFuture = Pin<Box<dyn Future<Output = T>>>
// No Send bound
// Use for single-threaded contexts
fn local_example() -> LocalBoxFuture<'static, String> {
let rc = std::rc::Rc::new("local data");
Box::pin(async move {
format!("Local: {}", rc)
})
}
// Cannot be sent to another thread
// tokio::spawn(local_example()); // ERROR: not SendLocalBoxFuture is the non-Send variant of BoxFuture.
use futures::future::BoxFuture;
use std::future::Future;
use std::pin::Pin;
// Boxing has a cost: heap allocation + dynamic dispatch
async fn boxing_cost() {
// No boxing - direct future
async fn direct() -> i32 { 1 }
direct().await;
// Boxing - heap allocation + vtable lookup
let boxed: BoxFuture<'static, i32> = Box::pin(async { 1 });
boxed.await;
// Both work, but direct is faster (no allocation)
}
// Only box when necessary:
// - Recursive functions
// - Trait objects
// - Heterogeneous collections
// - Conditional returnsBoxing has overhead; only use when dynamic dispatch is needed.
use futures::future::BoxFuture;
use std::future::Future;
use std::pin::Pin;
// tokio::spawn requires Send + 'static
async fn spawn_example() {
// BoxFuture implements Send
let fut: BoxFuture<'static, String> = Box::pin(async { "hello".to_string() });
tokio::spawn(fut);
// Pin<Box<dyn Future>> without Send won't work
// let fut: Pin<Box<dyn Future<Output = String>>> = Box::pin(async { "hello".to_string() });
// tokio::spawn(fut); // ERROR: future doesn't implement Send
}BoxFuture works with tokio::spawn; raw type may not.
| Type | Send | Use Case |
|------|------|----------|
| BoxFuture<'a, T> | Yes | Multi-threaded async, traits |
| LocalBoxFuture<'a, T> | No | Single-threaded async |
| Pin<Box<dyn Future<Output = T> + Send + 'a>> | Yes | Custom configuration |
| Pin<Box<dyn Future<Output = T> + 'a>> | No | Non-Send without alias |
use futures::future::BoxFuture;
// Common pattern: trait with async methods
trait Database {
fn get_user(&self, id: u64) -> BoxFuture<'static, Option<String>>;
fn save_user(&self, id: u64, name: String) -> BoxFuture<'static, bool>;
}
struct PostgresDatabase;
impl Database for PostgresDatabase {
fn get_user(&self, id: u64) -> BoxFuture<'static, Option<String>> {
Box::pin(async move {
// Simulated database query
Some(format!("User {}", id))
})
}
fn save_user(&self, id: u64, name: String) -> BoxFuture<'static, bool> {
Box::pin(async move {
// Simulated database save
true
})
}
}
async fn use_database(db: &dyn Database) {
let user = db.get_user(1).await;
println!("{:?}", user);
}Traits with async methods commonly use BoxFuture.
BoxFuture advantages:
spawn)Pin<Box> advantages:
When to use BoxFuture:
When to use LocalBoxFuture:
Rc, RefCell, etc.)When to use raw Pin<Box>:
Key insight: BoxFuture is ergonomics for the common case. The underlying type is identical to Pin<Box<dyn Future<Output = T> + Send + 'a>>, but the alias makes the common pattern concise and readable. Use the alias when it fits; use the raw type when you need different bounds.