如何理解返回另一个函数的Rust函数?

时间:2017-07-26 14:56:05

标签: rust

关于Rust的reading,我遇到了一个带有数字的示例函数,并返回一个将该数字添加到另一个数字的函数。

fn higher_order_fn_return<'a>(step_value: &'a i32) -> Box<Fn(i32) -> i32 + 'a> {
    Box::new(move |x: i32| x + step_value)
}

这里有很多特定于Rust的机制,我无法理解它。我敢肯定其中一些与生命周期管理有关,但必须以这种方式编写的原因使我无法理解。几个问题:

  • 为什么step_value作为参考传递?
  • 为什么要返回的函数被装箱?
  • 如何解释编写函数类型的非常规方法(如Fn(i32) -> i32 + 'a)?
  • 为什么'a被写为通用(<'a>)但在返回类型(+ 'a)中“已添加”?
  • move的含义是什么?这里有什么意思?

2 个答案:

答案 0 :(得分:6)

禁止提出多个问题,但由于这些都属于“这段代码意味着什么”,我不会抱怨。此外,它确实碰巧将相当多的怪异压缩成一个相对较小的,非常不寻常的代码片段。

  

为什么step_value作为参考传递?

不知道。它就是。 可以传递by-value而不会显着改变代码的语义。但 通过引用传递,这是所有其他与生命相关的问题的原因。

  

为什么要返回的函数被装箱?

它没有返回功能。函数由fn定义。它正在关闭。问题在于,出于性能原因,每个闭包实际上都是匿名类型的实例(有时称为“Voldemort类型”)。匿名类型是一个问题,因为您无法命名它们,但您来命名您的返回类型。

解决这个问题的方法是返回一个特征对象。在这种情况下,它返回Fn。还有FnMutFnOnce。它将它返回盒装,因为裸的特征对象不能按值传递,因此特征对象总是必须在某种指针后面(Box,{{1} },&等。)。

它们不能通过值传递,因为编译器无法确定它有多大,这使得它们几乎不可能移动它们。在那之后,一系列逻辑直接转向“编译器如何实现”领域,这在某种程度上超出了范围。

  

如何解释编写函数类型的非常规方法(如Rc)?

没有什么不同寻常的。不管怎么说,不是Rust,因为这是在Rust中,其他语言的其他方式与之无关。

让我们暂时忽略Fn(i32) -> i32 + 'a,因为那实际上是别的东西。 + 'a是重要的部分。 Rust中的每个“可调用”事物都实现了一个或多个Fn(i32) -> i32FnFnMut特征,这就是Rust表达能够调用某些东西的想法。 parens中的东西是参数,FnOnce之后的东西是返回类型,就像函数一样。

您可以在问题"When does a closure implement Fn, FnMut and FnOnce?"中了解有关这些特征的更多信息。

  

为什么->被写为通用('a)但在返回类型(<'a>)中“已添加”?

首先,因为生命周期是类型系统的一部分。因此,它们进入通用参数列表(+ 'a内的事物)。

其次,因为编译器必须了解<...>内的特征对象有效期多长时间。如果您有Box,编译器允许该值存在多长时间?通常,该信息将是该类型的一部分,但如果您使用的是特征,则编译器不会知道正在使用哪种类型。请注意,您可以{em} Box<SomeTrait> Box<SomeTrait> Box<T> T SomeTrait实现step_value

在这种情况下,闭包将继续保持'a借用,这意味着不得超过借用的生命周期(Box<Fn(i32) -> i32>)。但如果类型只是 + 'a,编译器将不会拥有该信息。因此,有一种语法可以指定无论隐藏在特征对象背后的类型是什么,它都不能超过给定的生命周期。

这就是Fn(i32) -> i32所说的:“这是一个实现'a特征的盒装值, 不能超过生命周期step_value”。

  

移动的含义是什么,移动到底是什么?

通常情况下,编译器会尝试猜测它必须做什么才能使闭包工作,但它并不能始终正确。在可能的情况下,它会尝试借用闭包捕获的东西。所以当你在闭包中使用step_value时,编译器通常会借用它。

这不会是一个问题,除了你正在从函数中返回闭包。这种自动借用仅持续功能的寿命,这不够长。要解决此问题,您可以移动到闭包中,而不是借用+ 'a

您可能想知道的好处。

  

如果你不在Box<Trait + 'a>中写&'a Trait,通常会发生什么?

实际上,编译器在这里有一个启发式。默认情况下,每个特征对象都有一个附加的生命周期。它是从包装它的指针继承而来的。因此,&'a (Trait + 'a)确实是Box'static没有自己的生命周期参数,因此Box<Trait> Box<Trait + 'static>'static),这意味着默认情况下,盒装特征对象不能包含任何非{{1}}借用。

答案 1 :(得分:2)

  

为什么step_value作为参考传递?

这没有充分的理由。按值传递它会使一切变得更容易。但是,有问题的示例可能已经完成了,因为您无法为每种类型执行此操作,只有那些Copy

  

为什么要返回的函数被装箱?

lambda的类型无法命名,因此无法从函数返回。所以你必须返回一个特征对象(Fn是一个特征),为此你需要一个盒子。 (使用impl Trait,您将不再需要该框。)

  

如何解释编写函数类型的非常规方法(如Fn(i32) -> i32 + 'a)

Fn有一些语法糖,其中语法Fn(arg1, arg2) -> ret是(我认为)Fn<(arg1, arg2), Output=ret>的简写。上面的+优先级低于错误,不属于Fn约束;相反,它是一个约束组合,意味着Box中的类型必须都是Fn(i32) -> i32并且具有生命周期'a

  

为什么'a被写为通用(<'a>)但在返回类型(+ 'a)中“已添加”?

生命周期参数必须在函数(或类型)的泛型参数部分中声明,因此<'a>。然后它出现在参数的引用类型(& 'a i32)中,最后作为Box中的附加约束。

  

move的含义是什么,以及此处的内容是什么?

它使闭包成为一个移动闭包,这意味着它捕获的东西被移动到闭包中而不是被引用捕获。但是,在这个例子中,请注意正在移动的是step_value,它本身就是一个参考!