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

时间:2018-09-08 20:17:36

标签: reference rust lifetime mutable

尽管'a'b的生存期彼此独立,但以下代码编译良好的原因是什么?

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

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

fn main() {}

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

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)长。为什么这不是错误?

3 个答案:

答案 0 :(得分:4)

  

尽管'a'b的生存期彼此独立,但以下代码编译良好的原因是什么?

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

原因是它们彼此相互独立。

如果&'a Foo<'b>超过'a,则类型'b是不可能的。因此,Rust借用检查器暗含地推断您必须打算使用'b: 'a'b'a寿命更长)。因此,上面的代码在语义上与此相同:

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

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

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
     

出现上述错误的原因是什么?

当您传递不可变引用时,它会被复制。在上面的示例中,这意味着&'b i32可以自己移动,并且它的活跃性与您从何处获得的无关。此复制的引用始终指向数据的原始地址。这就是第一个示例起作用的原因-即使x被删除,原始引用仍然有效。

当您绕过一个 mutable 引用时,它会被 moved 移动。这种情况的结果是,引用仍然是“通过”变量x。如果不是这种情况,则xFoo内容的可变引用可以与此新的不可变引用同时存在(不允许这样做)。因此,在这种情况下,引用不能超过'a-或换句话说:'a: 'b

但是我们不是已经说过'b: 'a吗?这里唯一的结论是'a'b必须与生存时间相同,这就是您先前的错误消息所要求的。

答案 1 :(得分:3)

  

出现上述错误的原因是什么?它认为是   可变引用的所有权,它会发现某物(来自Foo)   正在被取出(具有独立的生命周期),而不是   可能,因此是错误?

由于可变借贷不是Copy,因此可变借贷确实不能从Foo移出。它是隐含地重新借来的:

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

但是,这不是问题的根源。首先,以下是func的四个版本的摘要(结构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已经为{{ 1}}通话。

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

func

将版本3与版本4交换可以显示在这种情况下仍处于活动状态的外部不可变借位如何防止第二个可变借位。新要求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); } 迫使外部不可变借项的生存期'a扩展为等于生存期'a: 'b

'b

答案 2 :(得分:0)

只需添加建议(有关问题的详细说明,请参见其他答案):

只要有可能,请不要过度设计并终身使用。

在这种情况下,所有生命周期都是明确的,这意味着4种情况(以及很多思考!)。

情况1(编译)

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

第2种情况(编译)

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

情况3(编译)

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
}