如何在一个用作函数结果的闭包中应用特征

时间:2015-12-03 10:18:28

标签: rust

我无法实现此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)
} 

2 个答案:

答案 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

解决此问题的最常用方法是添加TBox<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'bT必须与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以获取更多信息。