How to bind lifetimes of Futures to fn arguments in Rust
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
When a Rust future borrows from a function argument, the future cannot outlive that borrowed value. The fix is not to fight the borrow checker, but to express the lifetime relationship clearly in the function signature or move owned data into the async task.
Why Lifetimes Matter for Futures
A future may be polled long after the function that created it has returned. If that future contains references to input arguments, Rust must know those references stay valid for the entire lifetime of the future.
That is why borrowed data and returned futures are often tied together with the same lifetime. In plain terms, if the future reads from text, then the future can live only as long as text lives.
Returning impl Future + 'a
For non-async fn APIs, an explicit lifetime bound is often the clearest pattern.
The + 'a part says the returned future may borrow data that is valid for lifetime 'a. Without that bound, the compiler cannot prove the future is safe to use.
Using async fn
An async fn can often express the same idea more simply because the compiler builds the future type for you.
Here the generated future implicitly carries lifetime 'a. The important rule is unchanged: the borrowed inputs must still exist until the future finishes.
When You Need a Boxed Future
Trait objects often require boxed futures. In that case, the lifetime must be attached to the box as well.
BoxFuture<'a, T> means the boxed future may borrow data valid for 'a. This is common in trait-based async abstractions.
When Borrowing Is the Wrong Choice
Sometimes the API you are calling needs a 'static future, such as when work is spawned onto a background executor. A future that borrows from a local argument usually cannot satisfy that requirement. In that case, move owned data into the async block instead.
Owning the String avoids lifetime coupling with the caller. If cloning is expensive, use Arc<str> or another shared ownership type rather than forcing a borrowed future into a 'static API.
Common Pitfalls
- Returning a future that borrows an argument without adding the matching lifetime bound leads to compiler errors. Tie the future lifetime to the borrowed inputs explicitly.
- Trying to pass a borrowing future into
tokio::spawnusually fails becausetokio::spawnneeds a'staticfuture. Move owned data into the task instead. - Borrowing local temporary values creates futures that outlive the temporary. Store the value in a named variable with a long enough lifetime.
- Boxing a future without the correct lifetime annotation hides the same ownership problem rather than solving it. Use
BoxFuture<'a, T>when the future borrows data. - Reaching for complex lifetime tricks too early makes the API harder to use. If ownership is acceptable, passing
String,Arc, or cloned values is often the cleaner design.
Summary
- A future that borrows from function arguments must not outlive those arguments.
- The usual fix is
impl Future<Output = T> + 'aor anasync fnwhose inputs carry the needed lifetime. - Boxed futures also need the lifetime attached, such as
BoxFuture<'a, T>. - Borrowing futures do not fit APIs that require
'statictasks. - When lifetime plumbing becomes awkward, switching to owned data is often the most practical solution.

