0

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:

  1. Read a record from the DB
  2. Call a passed-in modify function
  3. Commit the modified record back to the DB conditionally (if it hasn't been concurrently modified by another agent)
  4. 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

Ogapo
  • 551
  • 5
  • 9
  • 1
    Does this answer your question? [Function taking an async closure that takes a reference and captures by reference](https://stackoverflow.com/questions/72657504/function-taking-an-async-closure-that-takes-a-reference-and-captures-by-referenc) The fundamental problem is the same: the closure captures a variable by reference and accepts a reference as a parameter, and the returned future captures both of those lifetimes. – cdhowie Apr 02 '23 at 00:29

0 Answers0