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

问题描述 投票:4回答:3

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

reference rust lifetime mutable
3个回答
3
投票

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

由于可变借款不是Copy,因此可变借款确实无法从Foo中移出。它隐含地不可逆转地重新借贷:

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可以合法地与anothor &'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);
}

使用版本4交换版本3显示了在这种情况下仍然是活动的外部不可变借用如何阻止第二次可变借用。外部不可变借款的终身'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

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被删除,原始参考仍然有效。

当你传递一个可变引用时,它会被移动。其结果是这种情况是引用仍然“通过”变量x。如果不是这种情况,xFoo内容的可变引用可能与这个新的不可变引用同时存在 - 这是不允许的。因此,在这种情况下,参考不能比'a更长寿 - 或换句话说:'a: 'b

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


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
}
© www.soinside.com 2019 - 2024. All rights reserved.