Rust E0507错误:无法在Fn闭包中移动捕获变量的解决方案咨询
Rust闭包所有权错误:无法在Fn闭包中移出捕获变量
问题场景
你封装了with_connection函数管理SQLite连接的生命周期,同时实现了select_one辅助函数,通过传入FnOnce闭包将数据库查询结果行映射为自定义对象,但编译时触发所有权错误。
原始代码
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); }
编译错误
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
错误原因
with_connection的参数被约束为impl Fn(&Connection) -> T,这意味着传入的闭包可以被多次调用。但你捕获的mapper是FnOnce类型——这类闭包只能被执行一次,执行时会消耗自身(所有权转移)。编译器不允许在支持多次调用的Fn闭包里移动FnOnce变量,因为如果闭包被重复调用,mapper已经被移走,会导致非法访问。
解决方法
最直接且合理的修复是修改with_connection的闭包约束为FnOnce,因为实际场景中这个闭包只会被调用一次(打开连接→执行操作→关闭连接,流程只走一遍):
fn with_connection<T>(action: impl FnOnce(&Connection) -> T) -> T { let connection = Connection::open_in_memory().unwrap(); let result = action(&connection); connection.close().unwrap(); result }
修改后,select_one中捕获mapper的闭包会被标记为只能执行一次,此时移动mapper到query_row中就符合所有权规则了——毕竟你原本就只打算用一次mapper。
内容的提问来源于stack exchange,提问作者Jon
相关产品推荐
相关产品推荐

