有一个禁止提问多个问题的规定,但由于所有问题都属于“这段代码的含义是什么”,所以我不会抱怨。此外,它确实将相当多的奇怪之处压缩成了一个相对较小、不太常见的片段。
为什么要将 step_value
作为引用传递?
不知道。它就是这样。如果按值传递,不会显著改变代码的语义。但它正在被引用传递,这就是所有其他与生存期相关的问题的原因。
为什么返回的函数被封装?
它没有返回函数。函数由 fn
定义。它返回一个闭包。问题在于每个闭包实际上都是一个匿名类型的实例(有时称为“伏地魔类型”),出于性能原因。匿名类型是个问题,因为你不能给它们命名,但你必须给你的返回类型命名。
绕过此问题的方法是返回一个特质对象。在这种情况下,它返回一个
Fn
。还有
FnMut
和
FnOnce
。它返回了一个盒装对象,因为裸的特质对象不能按值传递,所以特质对象
总是在某种指针的后面(可以是
Box
,
&
,
Rc
等)。
它们不能按值传递,因为编译器无法计算出它的大小,这使得它们的移动几乎不可能。之后,逻辑的训练直接进入了“编译器如何实现”的领域,这在此处有些超出范围。
如何解释非常规的函数类型写法(如Fn(i32) -> i32 + 'a
)?
对于Rust来说,这并不不寻常。其他语言如何做与此无关。
让我们暂时忽略
+ 'a
,因为那实际上是另一回事。
Fn(i32) -> i32
是重要的部分。 Rust中的每个“可调用”对象都实现了一个或多个
Fn
,
FnMut
和
FnOnce
trait,这就是Rust表达能够调用某些东西的方式。括号内的内容是参数,
->
后面的内容是返回类型,就像函数一样。
您可以在问题
"When does a closure implement Fn, FnMut and FnOnce?"中了解有关这些trait的更多信息。
由于生命周期是类型系统的一部分,因此首先它们会放在泛型参数列表中(即
<...>
内的内容)。
其次,编译器需要理解
Box
中的特质对象存在的有效期。如果你有一个
Box<SomeTrait>
,编译器会允许该值存在多久?通常,这些信息都应包含在类型中,但如果使用了特质,则编译器不知道使用的是哪种类型。记住,你可以从任何一个实现了
SomeTrait
的
Box<T>
创建一个
Box<SomeTrait>
。
在这种情况下,闭包将保持对
step_value
的借用,这意味着它不能超出该借用的生命周期(也就是
'a
生命周期)。但如果类型仅为
Box<Fn(i32) -> i32>
,则编译器无法获得该信息。因此,有一种语法可指定某个类型隐藏在特质对象之后时,它不会超出给定的生命周期。
这就是
+ 'a
的含义:“这是一个实现了
Fn(i32) -> i32
trait的封装值,它
不能超出生命周期
'a
”。通常情况下,编译器会尝试猜测如何使闭包起作用,但并不总是正确的。在可能的情况下,它会尝试借用闭包捕获的内容。因此,当你在闭包内使用
step_value
时,编译器通常只会借用它。这本来不是问题,
除了你要将闭包返回到函数外部。这种自动借用只能持续函数的生命周期,而这不够长。为了解决这个问题,你可以将
step_value
移动到闭包中,而不是借用它。如果你在
Box<Trait + 'a>
中不写
+ 'a
,通常会发生什么?
实际上,编译器在这里有一个启发式的方法。默认情况下,每个特质对象都有一个附加的生命周期。它继承自包装它的指针。因此,&'a Trait 实际上是&'a (Trait + 'a)。Box没有自己的生命周期参数,因此它得到了static (即Box是Box),这意味着默认情况下,盒装特质对象不能包含任何非static借用。
step_value
是按引用传递的,仅仅是为了展示一个具有非平凡生命周期的例子。 - Boiethios