通用版本存在几个问题。
首先,您提供的错误是因为仅T: Add 不足以指定输出类型:您还需要对关联类型<T as Add>::Output 施加约束(请参阅Add 文档):
fn f<T: Add<Output=T>>(n: T) -> Box<Fn(T) -> T> {
Box::new(move |i: T| n + i)
}
或者,你可以让闭包返回<T as Add>的输出类型:
fn f<T: Add>(n: T) -> Box<Fn(T) -> T::Output> {
Box::new(move |i: T| n + i)
}
但是,现在您将收到以下错误:
<anon>:4:10: 4:37 error: the parameter type `T` may not live long enough [E0310]
<anon>:4 Box::new(move |i: T| n + i)
^~~~~~~~~~~~~~~~~~~~~~~~~~~
<anon>:4:10: 4:37 help: see the detailed explanation for E0310
<anon>:4:10: 4:37 help: consider adding an explicit lifetime bound `T: 'static`...
<anon>:4:10: 4:37 note: ...so that the type `[closure@<anon>:4:19: 4:36 n:T]` will meet its required lifetime bounds
<anon>:4 Box::new(move |i: T| n + i)
^~~~~~~~~~~~~~~~~~~~~~~~~~~
这里的问题是T 内部可能包含引用(之后,它是一个泛型类型 - 它可以包含任何东西);然而,Box<Fn(T) -> T> 隐含地意味着这个特征对象内的任何东西都必须是'static,即编译器自动添加'static 约束:Box<Fn(T) -> T + 'static>。但是,您的闭包会捕获 T,它可以包含任何引用,而不仅仅是 'static。
修复它的最通用方法是添加T 和Box<Fn(T) -> T> 的显式生命周期约束:
fn f<'a, T: Add<Output=T> + 'a>(n: T) -> Box<Fn(T) -> T + 'a> {
Box::new(move |i: T| n + i)
}
或者,您可以指定 T 为 'static,但这会不必要地限制代码的通用性:
fn f<T: Add<Output=T> + 'static>(n: T) -> Box<Fn(T) -> T> {
Box::new(move |i: T| n + i)
}
但是,这仍然无法编译:
<anon>:4:31: 4:32 error: cannot move out of captured outer variable in an `Fn` closure
<anon>:4 Box::new(move |i: T| n + i)
^
这个错误的发生是因为 Rust 中的加法(即Add trait)按值起作用——它消耗了两个参数。对于Copy 类型,比如数字,这很好——它们总是被复制的。但是,编译器不能假设泛型类型参数也指定Copy 类型,因为没有相应的界限,因此它假设T 类型的值只能移动。但是,您指定返回的闭包是 Fn,因此它通过引用获取其环境。您无法移出引用,这就是此错误的原因。
有几种方法可以修复这个错误,最简单的一种是添加Copy bound:
fn f<'a, T: Add<Output=T> + Copy + 'a>(n: T) -> Box<Fn(T) -> T + 'a> {
Box::new(move |i: T| n + i)
}
现在它可以编译了。
一种可能的替代方法是返回 FnOnce 闭包,它通过值获取其环境:
fn f<'a, T: Add<Output=T> + 'a>(n: T) -> Box<FnOnce(T) -> T + 'a> {
Box::new(move |i: T| n + i)
}
但是,它有两个问题。首先,顾名思义,FnOnce 只能被调用一次,因为在它第一次调用时它的环境就被消耗掉了,下次就没有什么可以调用它了。这可能过于局限。其次,不幸的是,Rust 根本无法调用 Box<FnOnce()> 闭包。这是一个实现问题,应该在未来解决;目前有一个不稳定的FnBox trait 可以解决这个问题。
甚至另一种选择是使用引用而不是值:
fn f<'a, T: 'a>(n: T) -> Box<Fn(T) -> T + 'a> where for<'b> &'b T: Add<T, Output=T> {
Box::new(move |i: T| &n + i)
}
现在我们指定&'b T 在任何生命周期内'b 必须与T 相加,而不是T。在这里,我们使用Add trait 被重载以引用原始类型的事实。这可能是这个函数最通用的版本。