struct Foo<'a> {
x: &'a i32,
y: &'a i32,
}
但是在同一个结构体中为不同的引用定义多个生命周期也是可能的:
struct Foo<'a, 'b> {
x: &'a i32,
y: &'b i32,
}
什么情况下这样做有用呢? 能否提供一些示例代码,当两个生命周期为'a
时无法编译,但当生命周期为'a
和'b
时可以编译(反之亦然)?
熬夜过后,我终于想出了一个例子,能够很好地说明生命周期的重要性。以下是代码:
static ZERO: i32 = 0;
struct Foo<'a, 'b> {
x: &'a i32,
y: &'b i32,
}
fn get_x_or_zero_ref<'a, 'b>(x: &'a i32, y: &'b i32) -> &'a i32 {
if *x > *y {
return x
} else {
return &ZERO
}
}
fn main() {
let x = 1;
let v;
{
let y = 2;
let f = Foo { x: &x, y: &y };
v = get_x_or_zero_ref(&f.x, &f.y);
}
println!("{}", *v);
}
如果您更改 Foo
的定义为:
struct Foo<'a> {
x: &'a i32,
y: &'a i32,
}
那么代码将无法编译。
基本上,如果您想在任何需要其参数具有不同生命周期的函数中使用结构体的字段,则结构体的字段也必须具有不同的生命周期。
由于这个问题在搜索结果中仍然很高,并且我感觉我可以更好地解释,因此我希望在这里重新回答我的问题。请考虑以下代码:
struct Foo<'a> {
x: &'a i32,
y: &'a i32,
}
fn main() {
let x = 1;
let v;
{
let y = 2;
let f = Foo { x: &x, y: &y };
v = f.x;
}
println!("{}", *v);
}
还有错误:
error[E0597]: `y` does not live long enough
--> src/main.rs:11:33
|
11 | let f = Foo { x: &x, y: &y };
| ^^ borrowed value does not live long enough
12 | v = f.x;
13 | }
| - `y` dropped here while still borrowed
14 | println!("{}", *v);
| -- borrow later used here
这里发生了什么?
f.x
的生命周期要求至少大到足以囊括x
的作用域直至println!
语句(因为它初始化为&x
,然后被赋值给v
)。Foo
的定义指定f.x
和f.y
都使用相同的泛型生命周期'a
,因此f.y
的生命周期必须至少与f.x
一样长。&y
分配给了f.y
,而y
在println!
之前就超出了作用域。错误!解决方法是允许Foo
使用不同的生命周期来处理f.x
和f.y
,我们使用多个泛型生命周期参数来实现:
struct Foo<'a, 'b> {
x: &'a i32,
y: &'b i32,
}
现在 f.x
和 f.y
的生命周期不再绑定在一起。 编译器仍将使用一个有效期至 println!
语句的生命周期来处理 f.x
。但是,f.y
不再需要使用相同的生命周期,因此编译器可以选择更小的生命周期用于 f.y
,例如仅对 y
的作用域有效的生命周期。这是另一个简单的例子,其中结构体定义必须使用两个生命周期才能按预期运行。它不会将聚合分成具有不同生命周期的字段,而是将结构体嵌套到另一个结构体中。
struct X<'a>(&'a i32);
struct Y<'a, 'b>(&'a X<'b>);
fn main() {
let z = 100;
//taking the inner field out of a temporary
let z1 = ((Y(&X(&z))).0).0;
assert!(*z1 == z);
}
结构体 Y
有两个生命周期参数,一个用于其包含的字段 &X
,另一个用于 X
的包含字段 &z
。
在操作 ((Y(&X(&z))).0).0
中,X(&z)
被创建为临时变量并被借用。它的生命周期仅限于此操作的范围,在语句结束时失效。但由于 X(&z)
的生命周期与其包含的字段 &z
不同,因此该操作可以返回 &z
,其值可以在函数中稍后访问。
如果对于 Y
结构体使用单一生命周期,则此操作将无法正常工作,因为 &z
的生命周期和其包含的结构体 X(&z)
相同,在语句结束时失效;因此返回的 &z
不再有效,无法后续访问。
请参见playground中的代码。
X(&z)
提升为自己的变量,即let x = X(&z)
,则可以删除对Y的额外生命周期。是否有其他方法来强制需要额外的生命周期参数?我目前正在尝试理解为什么函数可能需要>1个生命周期参数。 - Steven Shawmain
中不必要的作用域,甚至可以删除结构体的第二个参数。https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f8c63e5f51df08323749230931883bf2 我已经记下了您美好的短语“概念游戏”,并将您的书添加到我的愿望清单中。 - Steven Shawstruct Container<'a> {
data: &'a str,
}
struct Processor<'a, 'b> {
container: &'a Container<'b>,
}
fn process<'a, 'b>(processor: &'a Processor<'a, 'b>) -> &'b str
where
'a: 'b,
{
processor.container.data
}
fn main() {
let data = "Hello, world!";
let container = Container { data: &data };
let processor = Processor { container: &container };
let result = process(&processor);
println!("Result: {}", result);
}
我认为值得一提的是,在Rust中,生命周期参数与其定义的作用域密切相关。通过分配适当的生命周期参数并添加约束,我们确保引用具有有效的生命周期,并避免悬空引用或引用超出其预期作用域的问题。
每个项目或引用都有自己的生命周期,确定其有效和可使用的时间长度。当我们为不同的项目指定不同的生命周期参数时,我们为每个项目赋予一个代表引用有效期的"票"。
考虑以下代码结构:
{
// Scope of item A
{
// Scope of item B
}
}
'b
的生命周期参数分配给返回类型,该返回类型对应于项目B的范围。然而,如果我们不正确处理,可能会出现潜在问题。项目B的生命周期将在其范围内结束,但是默认情况下,项目A的生命周期比项目B的生命周期更长。'b
,该生命周期对应于项目B的范围。然而,由'a
表示的项目A的生命周期超过了返回类型的需求。实质上,我们给予返回类型一个有效期比项目A的实际生命周期更短的票。'a
生命周期添加约束,表明与'a
相关联的票只在项目B的范围内有效。通过添加这个约束,我们确保返回的引用不会超出其预期的范围,并保持生命周期之间的正确关系。static ZERO: i32 = 0;
struct Foo<'a, 'b> {
x: &'a i32,
y: &'b i32,
}
// returning lifetime 'b
fn get_x_or_zero_ref<'a, 'b>(x: &'a i32, y: &'b i32) -> &'b i32
where
// Adding constrain so a will not outlive b
'a: 'b,
{
if *x > *y {
x
} else {
&ZERO
}
}
fn main() {
let x = 1;
let v;
{
let y = 2;
let f = Foo { x: &x, y: &y };
v = get_x_or_zero_ref(&f.x, &f.y);
println!("{}", *v);
}
}