Cannot move a captured variable in an `Fn` closure

58 Views Asked by At

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

There are 1 best solutions below

0
Jon On

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.