< 返回版块

QingJuBaiTang 发表于 2024-01-30 00:09

下面这一段rust代码,self.target的生命周期是'b而不是'a呢?

pub struct Writer<'a> {
    target: &'a mut String,
}

impl<'a> Writer<'a> {
    fn indent<'b>(&'b mut self) -> &'a String {
        self.target
    }
}

编译器报错: error: lifetime may not live long enough --> src/bin/main29.rs:19:9 | 17 | impl<'a> Writer<'a> { | -- lifetime 'a defined here 18 | fn indent<'b>(&'b mut self) -> &'a String { | -- lifetime 'b defined here 19 | self.target | ^^^^^^^^^^^ method was supposed to return data with lifetime 'a but it is returning data with lifetime 'b | = help: consider adding the following bound: 'b: 'a

评论区

写评论
zylthinking 2024-01-30 12:22

我觉得这个彻底搞清楚了,生命周期也基本就差不多了。

首先是结论:

  • 通过 &'a &'b mut T 这种形式, 无论你怎么做, 都无法获得 &'b T 或者 &'b mut T
  • 通过 &'a &'b T 这种形式, 则可以获得
  • 通过 &'a mut &'b T 同样可以获得

然后是为什么

首先, 生命周期分 liveness scope 与 lifetime, 这两个虽然比较类似,但不是一回事。

对于一个 let x = &T 来说, liveness 起始于定义, 中止于最后一次访问, 若还实现了 drop, 则中止于主动 drop(T) 或者 containing block 中止。

lifetime 就是常见的 'a 的东西。

关键: liveness scope 并不一定等于lifetime.

lifetime的取值一般是编译器自由确定的,编译器一般会将lifetime设置为与 liveness scope 相同的值。 比如, let x = &'a T, 编译器基本不会将 'a 设置为 x 的 liveness scope 不同的值。

所以在感官上, 就造成了 x 存在, 在 'a 存在, x drop 了, 则 'a 也就不存在了的印象。但这个并非永远成立。

从另一个方面来说, liveness scope 与 'a 是编译期概念, 只有编译器会关心,并且一旦确定, 就不会改变。 编译器用他们确定是否编译失败。

回到原来的问题, 为啥 通过 &'a &'b mut T 这种形式, 无论你怎么做, 都无法获得 &'b T 或者 &'b mut T 呢?

因为 liveness scope 与 'a 并不总是一致, 因此, 存在一种可能。 继续用 let x = &'a T 这个例子, 那么就是 x 已经消失了, 但 'a 还在。 虽然在代码上你看不到它了, 但编译器看得到。

在原来问题中, 你一旦获得 &'b T, 则编译器在 lifetime 'b 范围内, 会认为存在这个引用。 那么考虑这段代码

let mut T = String::from("1");
let s1 = &'b mut T;
let s2 = &'a s1;
{
    let s3 = reborrow_via_ref(s2);
}

*s1 = String::from("2");

若允许通过某种方法获得引用 &'b T, 则最终编译会失败, 因为 s3 虽然没了, 但编译器认为存在一个看不见的引用, 这个引用生命期为 'b, 恰好 s1 持有的引用 lifetime 也是 'b, 这就违反了 mut ref 只能有一个实例时才能写的原则。

而这看起来很诡异, 因为从正常角度看来, *s1 = String::from("2")并没有违反引用的约定,不该编译不了。

其余情况可以仿照上面分析, 最终就是上面的结论。

最后, 一个现实的例子:

fn indent<'b>(s: &'b mut &'b mut String) -> &'b String {
    *s
}

fn main(){
    let mut x = String::from("x");
    let mut s = &mut x;
    {
        let s3 = indent(&mut s);
    }
    
    *s = String::from("xx"); // compiling fails
    println!("{} h {}", s, s);
}

--
👇
QingJuBaiTang: 如果把target声明称immutable &,就可以用返回'a,这是为什么

苦瓜小仔 2024-01-30 11:38

这是 NLL 针对重新借用添加约束的问题。

支撑 lvalue 的前缀是通过剥离字段和解引用做到的,除非我们在到达共享引用的解引用时停止。

访问 self.target 时,其 supporting prefix 会根据 target: &'a {mut} String 的类型而有所不同:

对于 fn indent<'b>(&'b mut self) -> &'a String { self.target } ,函数返回对 target 字段的 reborrow,并且

  1. target: &'a mut Stringself.target 被 reborrow 时,其支撑前缀为 self.targetself,在添加 self 进来的时候,self 携带的 'b 需要形成一个 'b: 'a 约束 —— 这表明 reborrow self.target 需要保证 &'b mut self 存活 —— 编译器告诉你需要这个约束而编译失败。
  2. target: &'a Stringself.target 被 reborrow 时,其支撑前缀仅为 self.target,因为对共享引用解引用时停止添加前缀,这意味着无需考虑 self (也就是 &'b mut self'b)存活,所以编译通过。

--
👇
QingJuBaiTang: 如果把target声明称immutable &,就可以用返回'a,这是为什么

作者 QingJuBaiTang 2024-01-30 10:12

如果把target声明称immutable &,就可以用返回'a,这是为什么

rayw0ng 2024-01-30 07:29

因为传进来的self变成'b了。

1 共 4 条评论, 1 页