Cannot borrow `self.conn` as mutable more than once at a time

Hi everyone,

I've been trying to figure out an error all day. I've tried many different approaches, but I just can't seem to fix it.

Here's my code:

use mysql_async::prelude::{Query, WithParams};
use mysql_async::Conn;
use mysql_async::Result;

pub struct MySQLLock {
    name: String,
    conn: Conn,
}

impl MySQLLock {
    pub fn new(name: &str, conn: Conn) -> Self {
        MySQLLock { name: name.to_string(), conn }
    }

    pub async fn try_lock<'a, F, Fut, R>(&'a mut self, func: F) -> Result<R>
    where
        F: FnOnce(bool, &'a mut Conn) -> Fut,
        Fut: Future<Output = Result<R>>,
    {
        // create lock in DB
        let locked: bool = "SELECT GET_LOCK(?,?)"
            .with((&self.name, 10))
            .first(&mut self.conn)
            .await?
            .unwrap_or(false);
        // exec func
        let result = func(locked, &mut self.conn).await?;
        // release lock in DB
        if locked {
            "SELECT RELEASE_LOCK(?)"
                .with((&self.name,))
                .ignore(&mut self.conn)
                .await?;
        }
        //
        Ok(result)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use mysql_async::Error;
    use mysql_async::prelude::Queryable;

    #[tokio::test]
    async fn test_lock() {
        let conn = Conn::from_url("mysql://root:@localhost:3306")
            .await
            .unwrap();
        let mut lock = MySQLLock::new("lock_name", conn);
        let _ = lock.try_lock(|locked, conn| async move {
            if !locked {
                return Err(Error::Other("Can't create lock".into()));
            }
            let one: u32 = conn.query_first("SELECT 1").await?.unwrap();
            let two: u32 = conn.query_first("SELECT 2").await?.unwrap();
            Ok((one, two))
        })
        .await
        .unwrap();
    }
}

And here's the error:

error[E0499]: cannot borrow `self.conn` as mutable more than once at a time
   |
15 |     pub async fn try_lock<'a, F, Fut, R>(&'a mut self, func: F) -> Result<R>
   |                           -- lifetime `'a` defined here
...
27 |         let result = func(locked, &mut self.conn).await?;
   |                      ----------------------------
   |                      |            |
   |                      |            first mutable borrow occurs here
   |                      argument requires that `self.conn` is borrowed for `'a`
...
32 |                 .ignore(&mut self.conn)
   |                         ^^^^^^^^^^^^^^ second mutable borrow occurs here

If I remove lifetime 'a here F: FnOnce(bool, &'a mut Conn) -> Fut, I get a other error, which I also can't figure out

error: lifetime may not live long enough
   |
52 |           let _ = lock.try_lock(|locked, conn| async move {
   |  ________________________________________-----_^
   | |                                        |   |
   | |                                        |   return type of closure `{async block@pool_core/src/lock.rs:52:46: 52:56}` contains a lifetime `'2`
   | |                                        has type `&'1 mut mysql_async::Conn`
53 | |             if !locked {
54 | |                 return Err(Error::Other("Can't create lock".into()));
...  |
58 | |             Ok((one, two))
59 | |         })
   | |_________^ returning this value requires that `'1` must outlive `'2`

I think I understand the errors, but I have no idea how to actually fix them.

Perhaps my structure/logic/thinking is wrong, and this should be approached differently in Rust?

When you use Fn* traits, use for <'a> FnOnce(&'a mut Conn) for the lifetime of the argument.

This for<'a> bit creates a new loan only for the duration of the call, and doesn't allow the closure to keep the connection.

If you use it with <'a> from some outer scope, it will mean that that you forbid having &'a mut Conn borrowed for anything shorter than this. It will be required to start the loan before try_lock is called, and keep it borrowed exclusively until after try_lock ends. The scope where <'a> is written is very important, and outer scopes create harsher restrictions.

You will also run into the problem that the future Fut returned by the callback is going to be tied to the lifetime of the temporary &mut Conn loan, but you don't have any syntax to declare that relationship. Use AsyncFnOnce trait, either from new Rust version or async_fn_traits crate.

1 Like

Thanks! AsyncFnOnce - this is all I need.