以下是代码:
fn main() {
let foo: Foo<i32> = Foo(42);
let bar: Foo<i64> = foo.map_ref(|x| *x as i64); // OK
let baz: Foo<&i32> = foo.map_ref(|x| x); // Error
}
struct Foo<T>(T);
impl<T> Foo<T> {
fn map_ref<U, F>(&self, f: F) -> Foo<U>
where
F: Fn(&T) -> U,
{
Foo(f(&self.0))
}
}
U
引用时编译失败:
error: lifetime may not live long enough
--> src\main.rs:4:48
|
4 | let baz: Foo<&i32> = foo.map_ref(|x: &i32| x); // Error
| - - ^ returning this value requires that `'1` must outlive `'2`
| | |
| | return type of closure is &'2 i32
| let's call the lifetime of this reference `'1`
|
help: dereference the return value
|
4 | let baz: Foo<&i32> = foo.map_ref(|x: &i32| *x); // Error
| +
我尝试引入一个通用的生命周期'a
但Foo::map_ref
不知道下一步该做什么。
对于这个简单的例子,你甚至不需要注释。相反,你可以通过澄清参数的生命周期应该/将至少与对的引用一样长来
U
帮助编译器:F
self
当你调用 时
foo.map_ref(f)
,实际上发生的事情是Foo::<T>::map_ref(&foo, f)
:调用方法会创建对调用该方法的对象的引用。所以现在,当我们调用map_ref
并给它一个闭包时,闭包会被赋予一个参数,其生命周期与隐式创建的 相同&foo
。请参阅Rust Playground。希望这能回答你的问题。:-)
编辑:回复您对HRTB 的评论:是的,您完全正确。
F
被脱糖为for <'b> Fn(&'b T) -> U
。这意味着 上的特征绑定F
变为“F
应为可调用函数,引用为任意生命周期'b
。”或者,如果我们想使用与错误消息相同的名称,“任意生命周期'1
。 ”实际上,你可以使用 rust-analyzer 通过打开生命周期省略提示,直接在 IDE 内部看到这种脱糖过程:
(
rust-analyzer.inlayHints.lifetimeElisionHints.enable
在 VS Code 中)。考虑到这一点,让我们重新分析编译器给出的错误消息:
就我个人而言,在考虑为什么这是一个错误时,我发现引入第二个
'2
生命周期有点令人困惑……但这只是我的看法。我向别人解释这一点的方式是说,“你试图'1
在闭包的返回类型中使用,这会强制'1
出现在中U
;但是,从外面看map_ref
,无法知道它有多长'1
。f
据我们所知,可以使用对局部变量的引用来调用它!”在这种情况下,这有点像我们要求编译器“延长”该局部变量的生命周期,以便我们可以保持指向Foo<U>
它的指针。显然,这违背了 Rust 的删除系统。我们需要明确注释的原因
'a
仅仅是因为这个例子不符合生命周期省略的规则。 所有引用都需要生命周期,只是编译器允许我们根据某些规则有时跳过编写它们。F
的参数需要某些东西,但编译器不会&self
自动使用 的生命周期;这样做对于大多数用例来说都过于保守(例如,因为f
现在不能通过对局部变量的引用来调用)。