【问题标题】:How to understand this Rust function that returns another function?如何理解这个返回另一个函数的 Rust 函数?
【发布时间】:2018-01-01 23:48:59
【问题描述】:

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

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) -&gt; i32 + 'a)?
  • 为什么 'a 写成泛型 (&lt;'a&gt;) 但“添加”在返回类型 (+ 'a) 中?
  • move 是什么意思,这里移了什么?

【问题讨论】:

    标签: rust


    【解决方案1】:

    禁止提出多个问题,但由于这些都属于“这段代码的含义”,我不会抱怨。此外,它确实碰巧将相当多的怪异压缩成一个相对较小、不是非常不寻常的 sn-p。

    为什么step_value 作为引用传入?

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

    为什么返回的函数是装箱的?

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

    解决这个问题的方法是返回一个 trait 对象。在这种情况下,它返回一个Fn。还有FnMutFnOnce。它将它装箱返回,因为裸 trait 对象不能按值传递,因此 trait 对象 总是 必须位于某种指针后面(即 Box&amp;、@987654329 @、)。

    它们不能按值传递,因为编译器无法计算出它的大小,这使得移动它们几乎是不可能的。之后,逻辑序列直接转向“编译器是如何实现的”领域,这有点超出了这里的范围。

    如何解释写函数类型的非常规方式(如Fn(i32) -&gt; i32 + 'a)?

    这并没有什么不寻常的地方。无论如何,不​​适用于 Rust,因为这是在 Rust 中,其他语言如何做到这一点并不相关。

    让我们暂时忽略+ 'a,因为这实际上是另一回事。 Fn(i32) -&gt; i32 是重要的部分。 Rust 中的每个“可调用”事物都实现了 FnFnMutFnOnce 特征中的一个或多个,这就是 Rust 表达能够调用事物的想法的方式。括号里面的东西是参数,-&gt; 后面的东西是返回类型,就像函数一样。

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

    为什么'a 写成泛型 (&lt;'a&gt;) 但“添加”在返回类型 (+ 'a) 中?

    首先,因为生命周期是类型系统的一部分。因此,它们进入通用参数列表(&lt;...&gt; 中的东西)。

    其次,因为编译器必须了解Box 中的特征对象的有效期。如果您有Box&lt;SomeTrait&gt;,编译器允许该值存在多长时间?通常,该信息将是类型的一部分,但如果您使用的是特征,那么编译器将不知道正在使用哪种类型。请记住,您可以在 any Box&lt;T&gt; 中创建 Box&lt;SomeTrait&gt;,其中 T 实现 SomeTrait

    在这种情况下,闭包将保留step_value借用,这意味着它不得超过该借用的生命周期(即'a)。但是如果类型是 just Box&lt;Fn(i32) -&gt; i32&gt;,编译器就不会拥有该信息。因此,有一种语法可以指定无论隐藏在 trait 对象后面的类型是什么,它不能超过给定的生命周期。

    这就是+ 'a 的意思:“这是一个实现Fn(i32) -&gt; i32 特征的装箱值,它不能'a 的生命周期长”。

    搬家是什么意思,这里搬的是什么?

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

    这不是问题,除了您要从函数中返回闭包。这种自动借用只会在函数的生命周期内持续,这还不够长。要解决此问题,您可以将其移动到闭包中,而不是借用 step_value

    您可能想知道的奖励。

    如果Box&lt;Trait + 'a&gt;不写+ 'a,一般会怎样?

    实际上,编译器在这里有一个启发式。默认情况下,每个 trait 对象都有一个附加的生命周期。它是从包装它的指针继承的。所以,&amp;'a Trait 真的是&amp;'a (Trait + 'a)Box 没有自己的生命周期参数,因此它得到 'staticie Box&lt;Trait&gt; is Box&lt;Trait + 'static&gt;),这意味着默认情况下,装箱的 trait 对象不能包含任何非'static 借用。

    【讨论】:

    • step_value 是通过引用传递的,只是展示了一个具有重要生命周期的示例。
    【解决方案2】:

    为什么step_value 作为引用传入?

    这没有充分的理由。按值传递它会使一切变得更容易。但是,有问题的示例可能已经这样做了,因为您不能对每种类型都这样做,只是那些Copy

    为什么返回的函数是装箱的?

    无法命名 lambda 的类型,因此无法从函数返回。所以你必须返回一个特征对象(Fn 是一个特征),并且你需要一个盒子。 (有了impl Trait,你就不再需要这个盒子了。)

    如何解释写函数类型的非常规方式(如Fn(i32) -&gt; i32 + 'a)

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

    为什么 'a 写成泛型 (&lt;'a&gt;) 但“添加”在返回类型 (+ 'a) 中?

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

    move 是什么意思,这里搬了什么?

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

    【讨论】:

      猜你喜欢
      • 2019-09-09
      • 2020-07-07
      • 2013-03-10
      • 1970-01-01
      • 2011-07-14
      • 1970-01-01
      • 1970-01-01
      • 2010-09-15
      • 1970-01-01
      相关资源
      最近更新 更多