Implementing lazy error contexts across await points
⚓ Rust 📅 2026-06-07 👤 surdeus 👁️ 1I would like to be able to implement an API surface similar to this:
pub async fn test_api() {
let mut resp = Response;
let result = may_fail().with_failure_context(|&mut resp| {}).await;
}
What I'm trying to do is to say, if may_fail fails, we will perform some function that mutates the Response, and then be able to halt control flow with ?. It is meant to be similar to context as provided by the anyhow crate.
My implementation looks something like this:
pub struct Response;
pub trait IntoApplicationResponse {}
impl IntoApplicationResponse for () {}
pub trait WithFailureContext<T, E>: Future<Output = Result<T, E>> {
fn with_failure_context<'lt, F>(self, f: F) -> WithFailureContextFuture<'lt, Self, F>
where
F: FnOnce(&'lt mut Response),
Self: Sized;
}
impl<T, E, U: Future<Output = Result<T, E>>> WithFailureContext<T, E> for U {
fn with_failure_context<'lt, F>(self, f: F) -> WithFailureContextFuture<'lt, Self, F>
where
F: FnOnce(&'lt mut Response),
Self: Sized,
{
todo!()
}
}
#[pin_project]
pub struct WithFailureContextFuture<'r, Fut, F> {
#[pin]
fut: Fut,
response: &'r mut Response,
context: Option<F>, // Option so we can take it out on poll
}
impl<'r, T, E, Fut, F> Future for WithFailureContextFuture<'r, Fut, F>
where
Fut: Future<Output = Result<T, E>>,
E: IntoApplicationResponse,
F: FnOnce(&mut Response),
{
type Output = Result<T, ()>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
match this.fut.poll(cx) {
Poll::Pending => Poll::Pending,
Poll::Ready(Ok(v)) => Poll::Ready(Ok(v)),
Poll::Ready(Err(_)) => {
if let Some(f) = this.context.take() {
f(this.response);
}
Poll::Ready(Err(()))
}
}
}
}
However, Rustc errors out by saying:
error: implementation of `FnOnce` is not general enough
--> src/lib.rs:14:66
|
14 | let result = may_fail().with_failure_context(|&mut resp| {}).await;
| ^^^^^ implementation of `FnOnce` is not general enough
|
= note: closure with signature `fn(&'2 mut Response)` must implement `FnOnce<(&'1 mut Response,)>`, for any lifetime `'1`...
= note: ...but it actually implements `FnOnce<(&'2 mut Response,)>`, for some specific lifetime `'2`
I would like to know whether this is a general limitation of the borrow checker, and maybe this is just something that cannot be implemented without unsafe, or my approach to the problem is wrong.
1 post - 1 participant
🏷️ Rust_feed