Rust中FnOnce与impl FnOnce作为函数参数的差异及报错解析
在第一段Rust代码中,将FnOnce作为泛型参数F定义在TransactionManager trait及create_vote函数中时,编译器会报类型不匹配错误:
pub trait TransactionManager<D, R, F>: DatabaseManager where D: DatabaseManager, F: FnOnce(D) -> Result<R, Error>, { fn execute_in_transaction(&mut self, f: F) -> Result<R, Error>; } pub async fn create_vote<D, T, F>(mut manager: T, data: VoteInsertion) -> Result<i32, Error> where D: DatabaseManager, T: TransactionManager<D, i32, F>, F: FnOnce(D) -> Result<i32, Error>, { manager.execute_in_transaction(|mut manager: D| manager.insert(data)) }
编译器报错信息:
mismatched types
expected type parameterF
found closure[closure@src/core/vote.rs:21:36: 21:52]
every closure has a distinct type and so could not always match the caller-chosen type of parameterF
但修改为第二段代码,在trait的方法参数中使用impl FnOnce后,代码可正常编译:
pub trait TransactionManager<D, R>: DatabaseManager where D: DatabaseManager, { fn execute_in_transaction(&mut self, f: impl FnOnce(D) -> Result<R, Error>) -> Result<R, Error>; } pub async fn create_vote<D, T>(mut manager: T, data: VoteInsertion) -> Result<i32, Error> where D: DatabaseManager, T: TransactionManager<D, i32>, { manager.execute_in_transaction(|mut manager: D| manager.insert(data)) }
疑问:我认为f: F也表示f必须实现FnOnce trait,为何必须使用impl FnOnce(D)来表达这一含义?
原因解析
第一种写法里,
TransactionManager把F作为trait的泛型参数,这意味着实现该trait的类型必须固定绑定一个特定的F类型。而create_vote函数的泛型F是由调用方指定的,但你在函数内部传入的是匿名闭包——每个闭包都是编译器生成的唯一匿名类型,不可能和调用者预先指定的F类型匹配,这就是报错的核心:闭包类型是独有的,无法满足调用者指定的泛型参数约束。第二种写法中,
impl FnOnce(D) -> Result<R, Error>是方法级的参数约束,它表示这个方法可以接受任意符合该FnOnce签名的类型,包括每个独有的闭包。此时trait不需要把F作为泛型参数,而是把参数类型的检查延迟到方法调用时,只要传入的闭包满足FnOnce的签名要求即可,完美适配闭包的匿名特性。
简单来说,第一种是把闭包类型绑定到了trait实现上,要求整个trait实现只能用同一种闭包类型;第二种是让方法灵活接受任何符合条件的闭包,两者的灵活性和适用场景完全不同。
内容的提问来源于stack exchange,提问作者wangjun

