禁止提出多个问题,但由于这些都属于“这段代码的含义”,我不会抱怨。此外,它确实碰巧将相当多的怪异压缩成一个相对较小、不是非常不寻常的 sn-p。
为什么step_value 作为引用传入?
不知道。就是这样。 可以按值传递,而不会显着改变代码的语义。但它是通过引用传递的,这就是所有其他与生命周期相关的问题的原因。
为什么返回的函数是装箱的?
它没有返回一个函数。函数由fn 定义。它正在返回一个闭包。问题在于,出于性能原因,每个闭包实际上都是匿名类型(有时称为“伏地魔类型”)的实例。匿名类型是个问题,因为您无法命名它们,但您必须为您的返回类型命名。
解决这个问题的方法是返回一个 trait 对象。在这种情况下,它返回一个Fn。还有FnMut和FnOnce。它将它装箱返回,因为裸 trait 对象不能按值传递,因此 trait 对象 总是 必须位于某种指针后面(即 Box、&、@987654329 @、等)。
它们不能按值传递,因为编译器无法计算出它的大小,这使得移动它们几乎是不可能的。之后,逻辑序列直接转向“编译器是如何实现的”领域,这有点超出了这里的范围。
如何解释写函数类型的非常规方式(如Fn(i32) -> i32 + 'a)?
这并没有什么不寻常的地方。无论如何,不适用于 Rust,因为这是在 Rust 中,其他语言如何做到这一点并不相关。
让我们暂时忽略+ 'a,因为这实际上是另一回事。 Fn(i32) -> i32 是重要的部分。 Rust 中的每个“可调用”事物都实现了 Fn、FnMut 和 FnOnce 特征中的一个或多个,这就是 Rust 表达能够调用事物的想法的方式。括号里面的东西是参数,-> 后面的东西是返回类型,就像函数一样。
您可以在问题"When does a closure implement Fn, FnMut and FnOnce?" 中了解有关这些特征的更多信息。
为什么'a 写成泛型 (<'a>) 但“添加”在返回类型 (+ 'a) 中?
首先,因为生命周期是类型系统的一部分。因此,它们进入通用参数列表(<...> 中的东西)。
其次,因为编译器必须了解Box 中的特征对象的有效期。如果您有Box<SomeTrait>,编译器允许该值存在多长时间?通常,该信息将是类型的一部分,但如果您使用的是特征,那么编译器将不知道正在使用哪种类型。请记住,您可以在 any Box<T> 中创建 Box<SomeTrait>,其中 T 实现 SomeTrait。
在这种情况下,闭包将保留step_value借用,这意味着它不得超过该借用的生命周期(即'a)。但是如果类型是 just Box<Fn(i32) -> i32>,编译器就不会拥有该信息。因此,有一种语法可以指定无论隐藏在 trait 对象后面的类型是什么,它不能超过给定的生命周期。
这就是+ 'a 的意思:“这是一个实现Fn(i32) -> i32 特征的装箱值,它不能比'a 的生命周期长”。
搬家是什么意思,这里搬的是什么?
通常,编译器会尝试猜测它必须做什么才能使闭包工作,但它并不总是正确。在可能的情况下,它会尝试借用闭包捕获的东西。所以当你在闭包中使用step_value 时,编译器通常会借用它。
这不是问题,除了您要从函数中返回闭包。这种自动借用只会在函数的生命周期内持续,这还不够长。要解决此问题,您可以将其移动到闭包中,而不是借用 step_value。
您可能想知道的奖励。
如果Box<Trait + 'a>不写+ 'a,一般会怎样?
实际上,编译器在这里有一个启发式。默认情况下,每个 trait 对象都有一个附加的生命周期。它是从包装它的指针继承的。所以,&'a Trait 真的是&'a (Trait + 'a)。 Box 没有自己的生命周期参数,因此它得到 'static(ie Box<Trait> is Box<Trait + 'static>),这意味着默认情况下,装箱的 trait 对象不能包含任何非'static 借用。