我无法实现这种LISP结构
(defun foo (n)
(lambda (i) (incf n i)))
Run Code Online (Sandbox Code Playgroud)
在Rust.我试过这个:
use std::ops::Add;
fn f<T: Add>(n: T) -> Box<Fn(T) -> T> {
Box::new(move |i: T| n + i)
}
fn main() {
let adder = f(2);
assert_eq!(4, adder(2));
}
Run Code Online (Sandbox Code Playgroud)
但它会导致错误:
error: mismatched types:
expected `T`,
found `<T as core::ops::Add>::Output`
(expected type parameter,
found associated type) [E0308]
Box::new(move |i: T| n + i)
^~~~~
Run Code Online (Sandbox Code Playgroud)
似乎Add为外部函数定义的特性没有转移到内部闭包中.
是否有可能实施这样的建设?
可以使用具体类型而不是泛型来实现此函数:
fn f(n: i32) -> Box<Fn(i32) -> i32> {
Box::new(move |i| n + i)
}
Run Code Online (Sandbox Code Playgroud)
通用版本存在几个问题.
首先,您提供的错误是因为仅仅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)
}
Run Code Online (Sandbox Code Playgroud)
或者,您可以使闭包返回以下输出类型<T as Add>:
fn f<T: Add>(n: T) -> Box<Fn(T) -> T::Output> {
Box::new(move |i: T| n + i)
}
Run Code Online (Sandbox Code Playgroud)
但是,现在您将收到以下错误:
<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)
^~~~~~~~~~~~~~~~~~~~~~~~~~~
Run Code Online (Sandbox Code Playgroud)
这里的问题是它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)
}
Run Code Online (Sandbox Code Playgroud)
或者,你可以指定T就是'static,虽然这不必要的限制了你的代码的通用性:
fn f<T: Add<Output=T> + 'static>(n: T) -> Box<Fn(T) -> T> {
Box::new(move |i: T| n + i)
}
Run Code Online (Sandbox Code Playgroud)
但是,这仍然无法编译:
<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)
^
Run Code Online (Sandbox Code Playgroud)
发生此错误是因为Rust(即Addtrait)中的添加按值工作 - 它消耗两个参数.对于Copy类型,如数字,它很好 - 它们总是被复制.但是,编译器不能假设泛型类型参数也指定Copy类型,因为没有相应的边界,因此它假定类型的值T只能移动.但是,您指定返回的闭包是Fn,因此它通过引用获取其环境.您无法移出引用,这就是此错误的含义.
有几种方法可以解决此错误,最简单的方法是添加Copy绑定:
fn f<'a, T: Add<Output=T> + Copy + 'a>(n: T) -> Box<Fn(T) -> T + 'a> {
Box::new(move |i: T| n + i)
}
Run Code Online (Sandbox Code Playgroud)
现在它编译.
一种可能的替代方法是返回FnOnce按值获取其环境的闭包:
fn f<'a, T: Add<Output=T> + 'a>(n: T) -> Box<FnOnce(T) -> T + 'a> {
Box::new(move |i: T| n + i)
}
Run Code Online (Sandbox Code Playgroud)
然而,它有两个问题.首先,正如其名称所暗示的那样,FnOnce只能调用一次,因为在第一次调用时,它的环境被消耗掉,并且下次没有任何东西可以调用它.这可能过于局限.其次,不幸的是,Rust根本无法调用Box<FnOnce()>闭包.这是一个实施问题,将来应该解决; 现在有一个不稳定的FnBox特点可以解决这个问题.
甚至另一种选择是使用引用而不是值:
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)
}
Run Code Online (Sandbox Code Playgroud)
现在我们指定代替T,&'b T对于任何生命周期'b必须与之相加T.这里我们使用Addtrait重载的事实来引用原始类型.这可能是此功能的最通用版本.