Search code examples
rustrust-sqlx

How to pass SQLX connection - a `&mut Trait` as a fn parameter in Rust


I need to generalize functions to pass an Executor trait for SQLX code. In the code below with a concrete &mut SqliteConnection parameter, main can call process, or it can call process_twice which calls process 2x times. All sqlx functions require arg type E: Executor.

I need to make my code generic so that conn: &mut SqliteConnection arg is also written with some generic, but so i can use it more than once.

Inside Sqlx, multiple structs implement Executor trait on a mutable reference, e.g.

impl<'c> Executor<'c> for &'c mut SqliteConnection {...}

I was able to convert a SINGLE call (the fn process), but not the fn process_twice - because executor is not copyable.

async fn process<'a, E>(conn: E) -> anyhow::Result<()>
where E: sqlx::Executor<'a, Database = sqlx::Sqlite> {...}

Full example

// [dependencies]
// anyhow = "1.0"
// futures = "0.3"
// sqlx = { version = "0.6", features = [ "sqlite", "runtime-tokio-native-tls"] }
// tokio = { version = "1.28.2", features = ["macros"] }
//
//  //// TO RUN, must set env var:
// DATABASE_URL='sqlite::memory:' cargo run

use futures::TryStreamExt;
use sqlx::SqliteConnection;
use sqlx::{query, Connection};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let mut conn = SqliteConnection::connect("sqlite::memory:").await?;
    process(&mut conn).await?;
    process_twice(&mut conn).await?;
    Ok(())
}

async fn process(conn: &mut SqliteConnection) -> anyhow::Result<()> {
    let sql = query!("SELECT name FROM sqlite_master");
    let mut rows = sql.fetch(conn);
    while let Some(row) = rows.try_next().await? {
        println!("{row:?}")
    }
    Ok(())
}

async fn process_twice(conn: &mut SqliteConnection) -> anyhow::Result<()> {
    process(conn).await?;
    process(conn).await?;
    Ok(())
}

Similar questions: this


Solution

  • The trick is to not parametrize the whole E but just the type behind the reference:

    async fn process_twice<T>(conn: &mut T) -> anyhow::Result<()>
    where
        for<'e> &'e mut T: Executor<'e, Database = Sqlite>,
    {
        process(&mut *conn).await?;
        process(conn).await
    }
    

    That way you can still reborrow the reference. That does mean that you can't take Pool<DB> any more because it only implements Executor for &Pool but should work for your usecase.