关于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
的含义是什么?这里有什么意思?答案 0 :(得分:6)
禁止提出多个问题,但由于这些都属于“这段代码意味着什么”,我不会抱怨。此外,它确实碰巧将相当多的怪异压缩成一个相对较小的,非常不寻常的代码片段。
为什么
step_value
作为参考传递?
不知道。它就是。 可以传递by-value而不会显着改变代码的语义。但 通过引用传递,这是所有其他与生命相关的问题的原因。
为什么要返回的函数被装箱?
它没有返回功能。函数由fn
定义。它正在关闭。问题在于,出于性能原因,每个闭包实际上都是匿名类型的实例(有时称为“Voldemort类型”)。匿名类型是一个问题,因为您无法命名它们,但您有来命名您的返回类型。
解决这个问题的方法是返回一个特征对象。在这种情况下,它返回Fn
。还有FnMut
和FnOnce
。它将它返回盒装,因为裸的特征对象不能按值传递,因此特征对象总是必须在某种指针后面(Box
,{{1} },&
,等。)。
它们不能通过值传递,因为编译器无法确定它有多大,这使得它们几乎不可能移动它们。在那之后,一系列逻辑直接转向“编译器如何实现”领域,这在某种程度上超出了范围。
如何解释编写函数类型的非常规方法(如
Rc
)?
没有什么不同寻常的。不管怎么说,不是Rust,因为这是在Rust中,其他语言的其他方式与之无关。
让我们暂时忽略Fn(i32) -> i32 + 'a
,因为那实际上是别的东西。 + 'a
是重要的部分。 Rust中的每个“可调用”事物都实现了一个或多个Fn(i32) -> i32
,Fn
和FnMut
特征,这就是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
,它本身就是一个参考!