以下是代码:
#[derive(Debug)]
struct Foo {
f: i32,
}
#[derive(Debug)]
struct Bar<'a> {
bar1: &'a Foo,
bar2: &'a Foo,
}
#[allow(unused_variables)]
fn make_bar<'a>(foo1: &'a Foo, foo2: &'a Foo) -> Bar<'a> {
Bar {
bar1: foo1,
bar2: foo2,
}
}
fn extract_bar2<'a>(foo: &'a Foo) -> &'a Foo {
let foo1 = Foo { f: 22 };
let foo2 = make_bar(&foo, &foo1).bar1;
foo2
}
fn main() {
let foo = Foo { f: 11 };
let foo1 = extract_bar2(&foo);
println!("foo1: {:?}", foo1);
}
这会出错:
error: `foo1` does not live long enough
--> src/main.rs:23:32
|>
23 |> let foo2 = make_bar(&foo, &foo1).bar1;
|> ^^^^
note: reference must be valid for the lifetime 'a as defined on the block at 21:45...
--> src/main.rs:21:46
|>
21 |> fn extract_bar2<'a>(foo: &'a Foo) -> &'a Foo {
|> ^
note: ...but borrowed value is only valid for the block suffix following statement 0 at 22:29
--> src/main.rs:22:30
|>
22 |> let foo1 = Foo { f: 22 };
|> ^
核心问题是:生命周期参数在结构的上下文中实际意味着什么?
更具体地说:为结构的所有字段设置相同的生命周期参数会产生什么后果?他们的生命必须完全一样吗?他们必须重叠吗?如果是这样,它们应该在多大程度上重叠?
以下两个结构之间的(语义和实际)差异是什么?
struct Bar<'b> {
bar1: &'b Foo,
bar2: &'b Foo,
}
struct Bar<'a, 'b> {
bar1: &'a Foo,
bar2: &'b Foo,
}
答案 0 :(得分:0)
之间的(语义和实践)差异是什么? 以下两个结构?
有一个小例子来证明差异:
#[derive(Debug)]
struct Foo;
#[derive(Debug)]
struct Bar1<'b> {
foo1: &'b Foo,
foo2: &'b Foo,
}
#[derive(Debug)]
struct Bar2<'a, 'b> {
foo1: &'a Foo,
foo2: &'b Foo,
}
fn main() {//'a -->
let foo1 = Foo;
let ref_foo1 =
{//'b -->
let foo2 = Foo;
//error: `foo2` does not live long enough
//replace the Bar1 with Bar2 in the row below to fix error
let bar = Bar1{foo1:&foo1, foo2:&foo2};
bar.foo1
};//--> 'b
println!("ref_foo1={:?}", ref_foo1);
}//--> 'a
Bar1
将其成员的生命周期截断为交叉点。因此,您无法从foo1
结构中获得对'a
生命周期Bar1
的引用。您将获得生命周期'b
的参考。
我应该注意,这种情况下的错误信息有点误导
答案 1 :(得分:0)
我将从reddit中粘贴一个我非常满意的答案。
编译器可以通过交叉来结合生命周期。也就是说,如果您有'a
和'b
,则会有'c
和'a
的交集'b
,在此期间两个生命周期都是“活着的”。我相信总是这样的情况,这个交叉点等于'a和'b中的最短点,因为确定范围有效,但也许我错了。
在实践中,这意味着,当您看到fn<'a>(x: &'a T, y: &'a U) -> &'a V
时,您可以加入&'static T
和&'b U
,然后您将获得&'b V
,因为'static
和'b
的交集是'b
。
那么,为什么你的方法导致编译器抱怨?因为编译器看起来像这样(它不是有效的语法):
fn extract_bar2<'a>(foo: &'a Foo) -> &'a Foo {
'b: {
let foo1 = Foo { f: 22 };
'c: { // The next line is wrong
let foo2: &'a Foo = make_bar<'a>(&'a foo, &'b foo1).bar1;
'd: {
return foo2;
}
}
}
}
我使范围更明确。怎么了?编译器知道foo2必须具有类型&'a Foo
,因为这是函数返回的内容。因此,make_bar返回的栏必须有生命周期'a
:否则我们无法获得&'a Foo
。所以我们必须打电话给make_bar<'a>
。但其中一个论点是错误的! foo2
没有生命周期'a
,它的有效期为'b
,但'a
已经过了 let foo2: &'b Foo = make_bar<'b>(&'a foo, &'b foo1).bar1;
。如果你采取交叉点并执行此操作:
foo2
然后Bar
与返回类型不匹配。
当您使用bar1
的第二个定义时,代码将起作用,因为bar2
和{{1}}在这种情况下不需要具有相同的生命周期。所以你的第二个Bar定义更加灵活,但在实践中你很少需要额外的灵活性,额外的生命周期注释很烦人。