如何在用作函数结果的闭包内应用trait

seb*_*ssa 1 rust

我无法实现这种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)

Vla*_*eev 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)
}
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.

修复它的最常用方法是添加明确的生命周期约束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)
}
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重载的事实来引用原始类型.这可能是此功能的最通用版本.