What is the difference between futures::future::select and select_all for racing multiple futures?
select races exactly two futures and returns which one completed along with its result, while select_all races an arbitrary collection of futures and returns the index of the first completed future along with remaining pending futures. The fundamental distinction is arity: select is a fixed two-future combinator returning (result, remaining_future), whereas select_all handles dynamic collections and returns (index, result, remaining_futures).
Basic select Usage
use futures::future::{self, select};
use futures::pin_mut;
async fn basic_select() {
let future1 = async { 1 };
let future2 = async { 2 };
// select races two futures
pin_mut!(future1, future2);
let result = select(future1, future2).await;
// result is Either<Left, Right>
match result {
future::Either::Left((value, _future2)) => {
println!("First completed with: {}", value);
}
future::Either::Right((value, _future1)) => {
println!("Second completed with: {}", value);
}
}
}select races exactly two futures and returns which completed first.
Basic select_all Usage
use futures::future::select_all;
use futures::pin_mut;
async fn basic_select_all() {
let futures = vec![
async { 1u32 },
async { 2 },
async { 3 },
async { 4 },
];
// select_all races a collection of futures
let (result, index, remaining) = select_all(futures).await;
println!("Future {} completed with: {}", index, result);
println!("Remaining futures: {}", remaining.len());
}select_all races an arbitrary number of futures and returns which index completed.
Return Types Compared
use futures::future::{self, select, select_all};
use futures::pin_mut;
async fn return_types() {
// select returns Either
let future1 = async { "first" };
let future2 = async { "second" };
pin_mut!(future1, future2);
let result: future::Either<(&str, impl Future<Output = &str>), (&str, impl Future<Output = &str>)> =
select(future1, future2).await;
// select_all returns (value, index, remaining)
let futures = vec![
async { "one" },
async { "two" },
async { "three" },
];
let (value, index, remaining): (&str, usize, Vec<impl Future<Output = &str>>) =
select_all(futures).await;
}select returns Either with the completed future and the other future; select_all returns the value, index, and remaining futures.
Handling Remaining Futures
use futures::future::{select, select_all};
use futures::pin_mut;
async fn remaining_futures() {
// select: One remaining future
let future1 = async { 1 };
let future2 = async { 2 };
pin_mut!(future1, future2);
match select(future1, future2).await {
future::Either::Left((result, remaining_future2)) => {
println!("First: {}", result);
// remaining_future2 is still pending, can be awaited
let _ = remaining_future2.await;
}
future::Either::Right((result, remaining_future1)) => {
println!("Second: {}", result);
let _ = remaining_future1.await;
}
}
// select_all: Collection of remaining futures
let futures = vec![
async { 1u32 },
async { 2 },
async { 3 },
];
let (result, index, remaining_futures) = select_all(futures).await;
println!("Index {} completed with {}", index, result);
println!("{} futures remaining", remaining_futures.len());
// Can continue racing remaining futures
if !remaining_futures.is_empty() {
let (result, index, _) = select_all(remaining_futures).await;
println!("Next completion at index {}", index);
}
}Both return the remaining futures, but select returns one future while select_all returns a collection.
Racing with Timeouts
use futures::future::{select, select_all};
use futures::pin_mut;
use tokio::time::{timeout, Duration};
async fn timeout_pattern() {
// select for two-future timeout
let operation = async {
tokio::time::sleep(Duration::from_secs(5)).await;
"completed"
};
let timeout_future = tokio::time::sleep(Duration::from_millis(100));
pin_mut!(operation, timeout_future);
match select(operation, timeout_future).await {
future::Either::Left((result, _)) => {
println!("Operation finished: {}", result);
}
future::Either::Right((_, _)) => {
println!("Timed out!");
}
}
// select_all for multiple operations with timeout
let operations = vec![
async { tokio::time::sleep(Duration::from_millis(50)).await; "fast" },
async { tokio::time::sleep(Duration::from_millis(200)).await; "slow" },
async { tokio::time::sleep(Duration::from_millis(100)).await; "medium" },
];
let timeout = Duration::from_millis(150);
match timeout(timeout, select_all(operations)).await {
Ok((result, index, _)) => {
println!("Operation {} finished first: {}", index, result);
}
Err(_) => {
println!("Timed out before any completed");
}
}
}Both combinators work well with timeout patterns for controlling concurrent operations.
Dynamic vs Fixed Collections
use futures::future::{select, select_all};
use futures::pin_mut;
async fn dynamic_collections() {
// select: Fixed at compile time
let future1 = async { 1 };
let future2 = async { 2 };
pin_mut!(future1, future2);
// Always exactly two futures
let _ = select(future1, future2).await;
// select_all: Runtime collection
let count = 5; // Could be determined at runtime
let mut futures: Vec<_> = (0..count)
.map(|i| async move { i * 2 })
.collect();
let (result, index, remaining) = select_all(futures).await;
// Empty collection behavior
let empty: Vec<async fn() -> u32> = vec![];
// select_all(empty).await; // Panics! No futures to race
// select with empty? Can't - needs exactly two futures
}select_all accepts dynamic collections; select requires exactly two futures at compile time.
Processing Multiple Completions
use futures::future::select_all;
use tokio::time::Duration;
async fn process_all_completions() {
let mut futures = vec![
async { tokio::time::sleep(Duration::from_millis(100)).await; "first" },
async { tokio::time::sleep(Duration::from_millis(200)).await; "second" },
async { tokio::time::sleep(Duration::from_millis(300)).await; "third" },
];
// Process completions in order they finish
let mut results = Vec::new();
let mut order = Vec::new();
while !futures.is_empty() {
let (result, index, remaining) = select_all(futures).await;
results.push(result);
order.push(index);
futures = remaining;
}
println!("Results in completion order: {:?}", results);
println!("Original indices: {:?}", order);
}select_all enables processing futures in completion order by re-racing remaining futures.
First Success Pattern
use futures::future::select_all;
async fn first_success() {
// Race multiple fallback endpoints
let endpoints = vec![
async {
tokio::time::sleep(Duration::from_millis(100)).await;
Err::<String, &str>("endpoint1 failed")
},
async {
tokio::time::sleep(Duration::from_millis(150)).await;
Ok::<String, &str>("endpoint2 succeeded")
},
async {
tokio::time::sleep(Duration::from_millis(200)).await;
Err::<String, &str>("endpoint3 failed")
},
];
let mut remaining = endpoints;
while !remaining.is_empty() {
let (result, index, rest) = select_all(remaining).await;
remaining = rest;
match result {
Ok(data) => {
println!("Success from endpoint {}: {}", index, data);
return; // First success, stop
}
Err(e) => {
println!("Endpoint {} failed: {}", index, e);
// Continue with remaining
}
}
}
println!("All endpoints failed");
}select_all supports patterns where you want the first successful result from multiple attempts.
Performance Characteristics
use futures::future::{select, select_all};
use futures::pin_mut;
async fn performance_characteristics() {
// select: No allocation, stack-based
// O(1) overhead for two futures
let future1 = async { 1 };
let future2 = async { 2 };
pin_mut!(future1, future2);
let _ = select(future1, future2).await;
// select_all: Vec allocation for remaining futures
// O(n) to manage collection
let futures = vec![
async { 1 },
async { 2 },
async { 3 },
async { 4 },
async { 5 },
];
let (_, _, remaining) = select_all(futures).await;
// remaining is a new Vec allocation
// For two futures, select is more efficient
// For dynamic collections, select_all is necessary
}select has lower overhead for two futures; select_all handles arbitrary collections with Vec allocations.
Error Handling
use futures::future::{select, select_all};
use futures::pin_mut;
async fn error_handling() {
// select: Handle Either with Result
let future1 = async { Ok::<_, &str>(1) };
let future2 = async { Err::<i32, &str>("error") };
pin_mut!(future1, future2);
let result = select(future1, future2).await;
match result {
future::Either::Left((Ok(v), _)) => println!("First succeeded: {}", v),
future::Either::Left((Err(e), _)) => println!("First failed: {}", e),
future::Either::Right((Ok(v), _)) => println!("Second succeeded: {}", v),
future::Either::Right((Err(e), _)) => println!("Second failed: {}", e),
}
// select_all: Handle result from any index
let futures = vec![
async { Ok::<_, &str>(1) },
async { Err::<i32, &str>("error") },
async { Ok(3) },
];
let (result, index, _) = select_all(futures).await;
match result {
Ok(v) => println!("Index {} succeeded: {}", index, v),
Err(e) => println!("Index {} failed: {}", index, e),
}
}Both propagate errors through their return types; handling depends on the specific pattern.
Cancellation Patterns
use futures::future::{select, select_all};
use futures::pin_mut;
use tokio::sync::oneshot;
async fn cancellation_pattern() {
// select: Cancel via channel
let (cancel_tx, cancel_rx) = oneshot::channel::<()>();
let operation = async {
loop {
tokio::time::sleep(Duration::from_millis(50)).await;
println!("Working...");
}
};
let cancel_future = async {
cancel_rx.await.ok();
println!("Cancelled");
};
pin_mut!(operation, cancel_future);
// Cancel after timeout (simulated)
tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(120)).await;
cancel_tx.send(()).unwrap();
});
match select(operation, cancel_future).await {
future::Either::Left((_, cancel_future)) => {
// Operation completed, cancel_future is still pending
drop(cancel_future); // Cancel it
}
future::Either::Right((_, operation)) => {
// Cancelled, operation is still pending
drop(operation); // Cancel it
}
}
// select_all: Cancel all remaining
let futures = vec![
async { tokio::time::sleep(Duration::from_secs(1)).await; "slow1" },
async { tokio::time::sleep(Duration::from_millis(10)).await; "fast" },
async { tokio::time::sleep(Duration::from_secs(1)).await; "slow2" },
];
let (_, _, remaining) = select_all(futures).await;
// remaining contains two pending futures
// Dropping them cancels them
drop(remaining);
}Both support cancellation; select_all naturally cancels remaining futures by dropping them.
Index Tracking
use futures::future::select_all;
async fn index_tracking() {
// Track which future completed
let requests = vec![
("api1", async {
tokio::time::sleep(Duration::from_millis(100)).await;
"response1"
}),
("api2", async {
tokio::time::sleep(Duration::from_millis(50)).await;
"response2"
}),
("api3", async {
tokio::time::sleep(Duration::from_millis(200)).await;
"response3"
}),
];
// Extract names for tracking
let names: Vec<_> = requests.iter().map(|(name, _)| name).collect();
let futures: Vec<_> = requests.into_iter().map(|(_, f)| f).collect();
let (result, index, _) = select_all(futures).await;
println!("{} returned: {}", names[index], result);
// api2 returned: response2
}select_all's index return enables correlating completions with their source.
Selecting All Results
use futures::future::select_all;
async fn selecting_all_results() {
// Common pattern: Wait for all, but in completion order
let futures = vec![
async {
tokio::time::sleep(Duration::from_millis(300)).await;
"third"
},
async {
tokio::time::sleep(Duration::from_millis(100)).await;
"first"
},
async {
tokio::time::sleep(Duration::from_millis(200)).await;
"second"
},
];
let mut all_results = Vec::new();
let mut remaining = futures;
let mut total_count = 0;
while !remaining.is_empty() {
let (result, index, rest) = select_all(remaining).await;
all_results.push((index, result));
remaining = rest;
total_count += 1;
}
// Results in completion order (not original order)
println!("Completed {} futures", total_count);
for (idx, result) in all_results {
println!(" Index {} completed with: {}", idx, result);
}
}Use select_all in a loop to process all futures in completion order.
When to Use Each
use futures::future::{select, select_all};
use futures::pin_mut;
async fn when_to_use() {
// Use select when:
// 1. Exactly two futures
// 2. Static composition known at compile time
// 3. Need maximum performance (no allocation)
let timeout = async {
tokio::time::sleep(Duration::from_secs(5)).await;
};
let operation = async {
// Some work
"result"
};
pin_mut!(timeout, operation);
// Clear two-future race
match select(operation, timeout).await {
future::Either::Left((result, _)) => {
println!("Completed: {}", result);
}
future::Either::Right((_, _)) => {
println!("Timed out");
}
}
// Use select_all when:
// 1. Variable number of futures
// 2. Collection determined at runtime
// 3. Need to track which index completed
// 4. Want to process in completion order
let mut requests: Vec<_> = (0..10)
.map(|i| async move {
tokio::time::sleep(Duration::from_millis(i * 10)).await;
format!("request_{}", i)
})
.collect();
// Process in completion order
while !requests.is_empty() {
let (result, idx, remaining) = select_all(requests).await;
println!("Request {} finished: {}", idx, result);
requests = remaining;
}
}Choose select for two-future scenarios; select_all for dynamic collections.
Synthesis
Quick comparison:
| Aspect | select |
select_all |
|---|---|---|
| Number of futures | Exactly 2 | Arbitrary (Vec) |
| Return type | Either<(result, other), (result, other)> |
(result, index, remaining) |
| Allocation | None | Vec for remaining |
| Index tracking | No (Left/Right) | Yes (numeric index) |
| Dynamic collections | No | Yes |
| Performance | Lower overhead | Vec allocation |
Common patterns:
use futures::future::{select, select_all};
use futures::pin_mut;
// Pattern: Timeout with select
async fn with_timeout<T>(future: impl Future<Output = T>, duration: Duration) -> Option<T> {
pin_mut!(future);
let timeout = tokio::time::sleep(duration);
pin_mut!(timeout);
match select(future, timeout).await {
future::Either::Left((result, _)) => Some(result),
future::Either::Right((_, _)) => None,
}
}
// Pattern: First success with select_all
async fn first_success<T, E>(futures: Vec<impl Future<Output = Result<T, E>>>) -> Result<T, Vec<E>> {
let mut remaining = futures;
let mut errors = Vec::new();
while !remaining.is_empty() {
let (result, _, rest) = select_all(remaining).await;
remaining = rest;
match result {
Ok(v) => return Ok(v),
Err(e) => errors.push(e),
}
}
Err(errors)
}Key insight: The choice between select and select_all fundamentally comes down to cardinality and dynamism. select is optimized for the common case of racing two futuresâtypically an operation against a timeout, or a computation against a cancellation signal. Its Either return type precisely encodes which future completed, with no runtime overhead for tracking indices. select_all handles the more complex case of racing an arbitrary number of futures, which is essential when the number of concurrent operations isn't known at compile time or when you need to correlate completions with their source. The key difference is what happens after completion: select gives you the completed result plus the remaining single future; select_all gives you the result, the index within the original collection, and all remaining pending futures. This makes select_all suitable for patterns like "process all futures in completion order" or "keep trying until one succeeds"âyou can re-race the remaining futures after each completion.
