In the following code, with_connection provides a wrapper around opening a connection to an sqlite database, ensuring the connection is closed correctly. This is used in the select_one helper to get an entry from the database. The call to select_one passes in a closure which maps the database row to the object. This closure is moved into the with_connection closure.
use rusqlite::{named_params, Connection, OptionalExtension, Row, ToSql};
use uuid::Uuid;
#[derive(Debug)]
struct User {
id: Uuid,
name: String,
}
fn select_one<T>(
query: &str,
params: &[(&str, &dyn ToSql)],
mapper: impl FnOnce(&Row) -> rusqlite::Result<T>,
) -> rusqlite::Result<Option<T>> {
with_connection(|connection| {
let mut statement = connection.prepare(query)?;
statement.query_row(params, mapper).optional()
})
}
fn with_connection<T>(action: impl Fn(&Connection) -> T) -> T {
let connection = Connection::open_in_memory().unwrap();
let result = action(&connection);
connection.close().unwrap();
result
}
fn main() {
let query = "select id, name from user;";
let params = named_params! {};
let user = select_one(query, params, |row| {
Ok(User { id: row.get(0)?, name: row.get(1)? })
});
dbg!(user);
}
This fails with the following error:
Compiling playground v0.0.1 (/playground)
error[E0507]: cannot move out of `mapper`, a captured variable in an `Fn` closure
--> src/main.rs:17:37
|
13 | mapper: impl FnOnce(&Row) -> rusqlite::Result<T>,
| ------ captured outer variable
14 | ) -> rusqlite::Result<Option<T>> {
15 | with_connection(|connection| {
| ------------ captured by this `Fn` closure
16 | let mut statement = connection.prepare(query)?;
17 | statement.query_row(params, mapper).optional()
| ^^^^^^ move occurs because `mapper` has type `impl FnOnce(&Row) -> rusqlite::Result<T>`, which does not implement the `Copy` trait
For more information about this error, try `rustc --explain E0507`.
error: could not compile `playground` (bin "playground") due to 1 previous error
I want to just pass the mapping closure through to the place where it's needed. I would expect it to be fine for query_row to take ownership of mapper since it's not used nor needed anywhere else, but I'm obviously missing something.
I found the answer in Cannot move out of captured outer variable in an `Fn` closure, specifically:
While the
mapperneeds to beFnMut()because it's being passed to a library function that expects it, thewith_connectionwrapper that was taking anFn()can be changed toFnOnce()since it's only expected to be called once.