我无法实现此LISP构造
(defun foo (n)
(lambda (i) (incf n i)))
在Rust,我试过了this:
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));
}
但它会导致错误:
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)
^~~~~
似乎为外部函数定义的特征Add
没有被转移到内部闭包中。
是否可以实施这样的结构?
可以使用具体类型而不是通用类型来实现此函数:
fn f(n: i32) -> Box<Fn(i32) -> i32> {
Box::new(move |i| n + i)
}
答案 0 :(得分:7)
通用版本存在一些问题。
首先,您提供的错误是因为仅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
特征)按值运行 - 它消耗两个参数。对于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)
}
现在它编译。</ p>
一种可能的替代方法是返回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
特性可以解决这个问题。
另一种替代方法是使用引用而不是值:
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)
}
现在我们指定T
代替&'b T
,'b
,T
必须与Add
相加。这里我们使用int pidarray[j];
m.m1_p1 = pidarray;
trait重载的事实来引用原始类型。这可能是此功能的最通用版本。
答案 1 :(得分:3)
如果您可以放弃功能调用,可以将x
包装在您自己的类型中:
use std::ops::Add;
struct Adder<X> {
x: X
}
impl<X: Copy> Adder<X> {
fn add<Y: Add<X>>(&self, y: Y) -> <Y as Add<X>>::Output {
y + self.x
}
}
fn main() {
let x = Adder { x: 1usize };
x.add(2); // as opposed to x(2)
}
这意味着你可以摆脱Box
而不需要分配任何东西。在稳定的Rust中无法实现Fn(..)
,并且在将来的Rust版本中,unstable版本可能会中断。请查看std::ops::Fn
以获取更多信息。