I'm working on a database library and I have a situation I can't figure out how to express with Rust lifetimes. I have a transact method which does the following:
- Read a record from the DB
- Call a passed-in modify function
- Commit the modified record back to the DB conditionally (if it hasn't been concurrently modified by another agent)
- If there was a collision, return to step 1 and repeat until there isn't one.
I want users to be able to pass a "modify" function to my async transact method.
I'm able to express this just fine when the modify function is synchronous (see below):
pub async fn transact<F, O : Sync + Send>(&mut self, doc_id: &str, modifier: F) -> Result<O>
where
F : Fn(&mut Doc) -> Result<O> + Sync + Send
{
loop {
let mut doc = self.read(doc_id).await?;
let out = modifier(&mut doc)?;
if self.commit(doc).await? {
return Ok(out);
}
}
}
which I call like this
// this one works fine
let ret = conn.transact("fake-doc", |doc| {
doc.value += referenced_var;
return Ok(doc.rvn);
}).await?;
But when I try to support the exact same flow, but allowing for an async modifier
function I get lifetime errors:
error: lifetime may not live long enough
--> src/main.rs:73:9
|
72 | let ret = conn.transact_async("fake-doc", |doc| {
| ---- return type of closure is Pin<Box<(dyn Future<Output = std::result::Result<i32, ()>> + Send + Sync + '2)>>
| |
| has type `&'1 mut Doc`
73 | / Box::pin(async {
74 | | doc.value += referenced_var;
75 | | return Ok(doc.rvn);
76 | | })
| |__________^ returning this value requires that `'1` must outlive `'2`
error[E0597]: `referenced_var` does not live long enough
--> src/main.rs:74:26
|
72 | let ret = conn.transact_async("fake-doc", |doc| {
| ----- value captured here
73 | / Box::pin(async {
74 | | doc.value += referenced_var;
| | ^^^^^^^^^^^^^^ borrowed value does not live long enough
75 | | return Ok(doc.rvn);
76 | | })
| |__________- returning this value requires that `referenced_var` is borrowed for `'static`
...
82 | }
| - `referenced_var` dropped here while still borrowed
Here's my attempt at a function signature and the call site:
pub async fn transact_async<F, O : Sync + Send>(&mut self, doc_id: &str, modifier: F) -> Result<O>
where
F : Fn(&mut Doc) -> Pin<Box<dyn Future<Output=Result<O>> + Sync + Send>> + Sync + Send
{
loop {
let mut doc = self.read(doc_id).await?;
let out = modifier(&mut doc).await?;
if self.commit(doc).await? {
return Ok(out);
}
}
}
// this one has lifetime issues...
let ret = conn.transact_async("fake-doc", |doc| {
Box::pin(async {
doc.value += referenced_var;
return Ok(doc.rvn);
})
}).await?;
In principle, it seems like all the lifetimes are actually the same (due to async await guarantees). I suspect I'm not expressing some bound correctly on transact_async
but I admit I don't grok Rust lifetimes well enough to know if it's even possible or not.
Here's a Rust Playground with the functional code if that makes anything clearer: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=556706a49506d6ba6074a43844841422