0

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.

asked Feb 24, 2024 at 18:31
1
  • Define the type of mapper as mapper: impl Fn(&Row) -> rusqlite::Result<T>, and pass the reference of mapper like statement.query_row(params, &mapper).optional(). So mapperis not moved. Commented Feb 24, 2024 at 19:34

1 Answer 1

0

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 a FnOnce() 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.

answered Feb 26, 2024 at 9:03
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.