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.
1 Answer 1
I found the answer in Cannot move out of captured outer variable in an `Fn` closure, specifically:
The problem with
Fn()is that you can call it multiple times. If you moved out of a captured value, that value would not be available anymore at the next call. You need aFnOnce()to make sure calling the closure also moves out of it, so it's gone and can't be called again.
While the mapper needs to be FnMut() because it's being passed to a library function that expects it, the with_connection wrapper that was taking an Fn() can be changed to FnOnce() since it's only expected to be called once.
mapperasmapper: impl Fn(&Row) -> rusqlite::Result<T>,and pass the reference ofmapperlikestatement.query_row(params, &mapper).optional(). Somapperis not moved.