从作为参数传递给函数的引用返回内部引用时的生命周期处理

Lifetimes' handling when returning inner reference from a reference passed as an argument to the function

尽管生命周期 'a'b 彼此独立,但以下代码编译正常的原因是什么?

struct Foo<'a> {
    i: &'a i32
}

fn func<'a, 'b>(x: &'a Foo<'b>) -> &'b i32 {
    x.i
}

fn main() {}

如果我将 Foo 中的引用 i 设为可变,则会出现以下错误。

5 | fn func<'a, 'b>(x: &'a Foo<'b>) -> &'b i32 {
  |                    -----------     -------
  |                    |
  |                    this parameter and the return type are declared with different lifetimes...
6 |     x.i
  |     ^^^ ...but data from `x` is returned here

出现上述错误的原因是什么?。它是否认为它是可变引用的所有权并且它看到某些东西(来自 Foo)正在被取出(具有独立的生命周期),这是不可能的,因此错误?

此代码(我认为会通过)也失败了:

struct Foo<'a> {
    i: &'a mut i32
}

fn func<'a, 'b: 'a>(x: &'a Foo<'b>) -> &'b i32 {
    x.i
}

fn main() {}

失败并出现错误:

 error[E0623]: lifetime mismatch
 --> src/main.rs:6:5
  |
5 | fn func<'a, 'b: 'a>(x: &'a Foo<'b>) -> &'b i32 {
  |                        -----------
  |                        |
  |                        these two types are declared with different lifetimes...
6 |     x.i
  |     ^^^ ...but data from `x` flows into `x` here

但是这个通过了:

struct Foo<'a> {
    i: &'a mut i32
}

fn func<'a: 'b, 'b>(x: &'a Foo<'b>) -> &'b i32 {
    x.i
}

fn main() {}

这对我来说似乎有点违反直觉。这里,外部生命周期 ('a) 可能比内部生命周期 ('b) 长。为什么这不是错误?

What is the reason it gives the above error?. Does it consider it's ownership over mutable reference and it sees that something (from Foo) is being taken out (with an independent lifetime), which is not possible, hence the error ?

可变借用确实不能从 Foo 中移出,因为可变借用不是 Copy。它被隐式地不可变地重新借用:

fn func <'a, 'b> (x:&'a Foo<'b>) -> &'b i32 {
    &*x.i
}

然而,这不是问题的根源。首先,这里总结一下func的四个版本(struct Foo与我的解释无关):

版本 1(编译):

fn func<'a, 'b>(x: &'a &'b i32) -> &'b i32 {
    x
}

版本 2(编译失败):

fn func<'a, 'b>(x: &'a &'b mut i32) -> &'b i32 {
    x
}

版本 3(编译失败):

fn func<'a, 'b: 'a>(x: &'a &'b mut i32) -> &'b i32 {
    x
}

版本 4(编译):

fn func<'a: 'b, 'b>(x: &'a &'b mut i32) -> &'b i32 {
    x
}

版本 2 和 3 失败,因为它们违反了禁止同时拥有对资源的可变引用和不可变引用的无别名规则。在这两个版本中,'b 可能 严格地 'a 长寿。因此,&'b mut i32&'b i32 可以共存。版本 1 可以编译,因为别名规则允许同时对资源进行多个不可变引用。因此,&'b i32 可以与另一个 &'b i32 合法共存。

乍一看,版本 4 应该会失败,因为在同一生命周期内再次存在可变借用和不可变借用。与版本 2 和版本 3 的区别在于,由于要求 'a: 'b,这次 'a 至少与 'b 一样长,这意味着 'b 可能 严格来说比 'a 长寿。只要生命周期 'a 持续,引用的 i32 就不能被可变地借用第二次(可变引用不是 Copy)——i32 已经被可变地借用给 func呼唤。

这是一个示例,演示了版本 2 和 3 如何导致未定义的行为:

fn func<'a, 'b: 'a>(x: &'a &'b mut String) -> &'b str {
    unsafe { std::mem::transmute(&**x as &str) } // force compilation
}

fn main() {
    let mut s = String::from("s");
    let mutref_s = &mut s;
    let ref_s = {
        let ref_mutref_s = &mutref_s;
        func(ref_mutref_s)
    };
    // use the mutable reference to invalidate the string slice       
    mutref_s.clear();
    mutref_s.shrink_to_fit();
    // use the invalidated string slice
    println!("{:?}", ref_s);
}

将版本 3 与版本 4 交换显示了在这种情况下仍处于活动状态的外部不可变借用如何阻止第二个可变借用。外部不可变借用的生命周期 'a 被新要求 'a: 'b 强制扩展为等于生命周期 'b:

error[E0502]: cannot borrow `*mutref_s` as mutable because `mutref_s` is also borrowed as immutable
  --> src/main.rs:20:5
   |
17 |         let ref_mutref_s = &mutref_s;
   |                             -------- immutable borrow occurs here
...
20 |     mutref_s.clear();
   |     ^^^^^^^^ mutable borrow occurs here
...
23 | }
   | - immutable borrow ends here

What is the reason the following code compiles fine, despite both the lifetimes 'a and 'b being independent of each other?

fn func<'a, 'b>(x: &'a Foo<'b>) -> &'b i32 {
    x.i
}

原因是它们不是相互独立的

如果 'a'b 长寿,类型 &'a Foo<'b> 将不可能存在。因此,Rust 借用检查器隐含地推断你 必须 打算 'b: 'a'b'a 长)。所以上面的代码在语义上与此相同:

fn func<'a, 'b: 'a>(x: &'a Foo<'b>) -> &'b i32 {
    x.i
}

If I make the reference i in Foo mutable, it gives the following error.

5 | fn func<'a, 'b>(x: &'a Foo<'b>) -> &'b i32 {
  |                    -----------     -------
  |                    |
  |                    this parameter and the return type are declared with different lifetimes...
6 |     x.i
  |     ^^^ ...but data from `x` is returned here

What is the reason it gives the above error?

当您传递一个 不可变 引用时,它会被 复制 。在上面的例子中,这意味着 &'b i32 可以自行移动,它的活跃度与你从哪里得到它无关。这个复制的引用总是指向数据的原始地址。这就是第一个示例有效的原因——即使 x 被删除,原始引用仍然有效。

当您传递 mutable 引用时,它会 moved。这种情况的结果是引用仍然“通过”变量 x。如果不是这种情况,xFoo 内容的可变引用可能与这个新的不可变引用同时存在——这是不允许的。因此,在这种情况下,引用不能超过 'a - 或者换句话说:'a: 'b.

但是我们不是已经说过 'b: 'a 了吗?这里唯一的结论是 'a'b 必须是 相同的生命周期,这是你之前的错误信息所要求的。

只是添加一个建议(有关您问题的详细解释,请参阅其他答案):

尽可能不要过度设计生命周期。

在这种情况下,明确所有生命周期意味着 4 种情况(并且需要大量思考!)。

案例一(编译)

fn func<'a, 'b: 'a>(x: &'a Foo<'b>) -> &'a i32 {
    x.i
}

案例二(编译)

fn func<'a: 'b, 'b>(x: &'a Foo<'b>) -> &'a i32 {
    x.i
}

案例三(编译)

fn func<'a: 'b, 'b>(x: &'a Foo<'b>) -> &'b i32 {
    x.i
}

案例4(不编译)

fn func<'a, 'b: 'a>(x: &'a Foo<'b>) -> &'b i32 {
    x.i
}

如果您使用匿名生命周期并让编译器构建生命周期区域之间的依赖关系,它就像这样简单:

fn func<'a>(x: &'a Foo) -> &'a i32 {
    x.i
}