< 返回版块

hitolz 发表于 2023-07-04 11:20

Tags:生命周期;

fn the_longest<'a, 'b: 'a>(s1: &'a str, s2: &'b str) -> &'a str {
    if s1.len() > s2.len() {
        s1
    } else {
        s2
    }
}
fn main() {
    let s1 = String::from("rust");
    let s1_r = &s1;
    {
        let s2 = String::from("C");
        let res = the_longest(s1_r, &s2);
        let res = the_longest(&s2, s1_r);
        println!("{} is the longest string", res);
    }
}

问题:

  1. 'a 'b 跟参数传参的顺序有没有关系?
  2. main 函数中调用了2次 the_longest,参数顺序不一样,为什么能编译通过?是否说明问题1生命周期跟参数顺序没关?如果跟参数顺序没关系,那 'a 'b 代表的不是入参的生命周期,那代表的是什么?

评论区

写评论
苦瓜小仔 2023-07-04 17:50

作用域的这种图是怎么画的

手打的。

作者 hitolz 2023-07-04 15:53

感谢回答。我在理解一下

顺便问一句,作用域的这种图是怎么画的

--
👇
苦瓜小仔: 首先你得分清生命周期、生命周期标注两个事情,其次你所指的生命周期是值的还是引用的?

对于 'a 和 'b,它们是函数上的生命周期标注,最重要的是表明多个生命周期之间约束关系(谁应该比谁活得更长[^1],或者说,谁不能在谁之前无效)。

[^1]: 注意,x 比 y 活得更长 ('x: 'y 或者说 'x outlives 'y) 是 x 的生命周期大于等于 y 的生命周期,而不是 x 的生命周期严格大于 y 的生命周期

而值的生命周期通常不会觉得混乱,因为 它就是一个值的作用域,从创建到释放的范围

引用的生命周期就是引用有效的范围,它可以有洞(非连续的)。

以下是 s1 和 s2 的值的范围及产生的引用

fn main() {
    let s1 = String::from("rust");// ---------------------s1
    let s1_r = &s1;//                                     | &'x s1
    {//                                                   |
        let s2 = String::from("C");// ----------------s2  |
        let res = the_longest(s1_r, &s2);//            |  | &'y s2
        let res = the_longest(&s2, s1_r);//            |  | &'z s2
        println!("{} is the longest string", res);//   |  |
    }// -----------------------------------------------|  |
}// ------------------------------------------------------|

注意,&'y s2 和 &'z s2 是临时的、传给函数的引用。'z 的有效范围不仅在 the_longest 函数内有效,并且可以延续到函数体之外(因为 the_longest 的生命周期标注)。

而生命周期标注 <'a, 'b: 'a>(s1: &'a str, s2: &'b str) -> &'a str 作为一种契约,规定了以下内容:

  1. 第二个引用至少和第一个引用活得一样长(或者说:第二个引用不能在第一个引用无效之前无效、第二个引用比第一个引用活得更长)
  2. 返回的引用至少和第一个引用活得一样长(或者说:返回的引用不能在第一个引用无效之前无效,这是因为 'a: 'a 恒成立)

那么我猜让你最困惑的点在于,对于 the_longest(s1_r, &s2),似乎不符合上述第一个约定 —— &s2 完全可以并且的确先于第一个引用无效之前无效。

这就涉及再借 (reborrow) 与型变 (variance):

  1. reborrow:通过重新借用来使用那个借用,得到一个更小生命周期的借用。 the_longest(s1_r, &s2) 实际上是 the_longest(&*s1_r, &s2),其中 &*s1_r 的生命周期比 s1_r 更小。
  2. 型变,大多数情况为生命周期的协变 (covariance),核心是将一个更大生命周期的引用(或者含引用的类型)当作更小生命周期的引用(或者含引用的类型)使用。&*s1_r 通过协变,可以被视为和 &s2 一样的生命周期(甚至比 &s2 更小生命周期),从而符合第一个约定。
作者 hitolz 2023-07-04 15:52

感谢回答。我在理解一下

--
👇
zylthinking: 额, 还有个前提, 就是你这代码

fn the_longest<'a, 'b: 'a>(s1: &'a str, s2: &'b str) -> &'a str { if s1.len() > s2.len() { s1 } else { s2 } }

其实编译器知道和下面的比, 除了适用情况更广外, 没有副作用

fn the_longest<'a>(s1: &'a str, s2: &'a str) -> &'a str { if s1.len() > s2.len() { s1 } else { s2 } }

于是实际上, 它就能将上面的代码当作下面的代码来处理

--
👇
zylthinking: 这里有个概念叫做协变, 理解了这个就一切清楚了 https://doc.rust-lang.org/nomicon/subtyping.html

苦瓜小仔 2023-07-04 13:14

首先你得分清生命周期、生命周期标注两个事情,其次你所指的生命周期是值的还是引用的?

对于 'a 和 'b,它们是函数上的生命周期标注,最重要的是表明多个生命周期之间约束关系(谁应该比谁活得更长[^1],或者说,谁不能在谁之前无效)。

[^1]: 注意,x 比 y 活得更长 ('x: 'y 或者说 'x outlives 'y) 是 x 的生命周期大于等于 y 的生命周期,而不是 x 的生命周期严格大于 y 的生命周期

而值的生命周期通常不会觉得混乱,因为 它就是一个值的作用域,从创建到释放的范围

引用的生命周期就是引用有效的范围,它可以有洞(非连续的)。

以下是 s1 和 s2 的值的范围及产生的引用

fn main() {
    let s1 = String::from("rust");// ---------------------s1
    let s1_r = &s1;//                                     | &'x s1
    {//                                                   |
        let s2 = String::from("C");// ----------------s2  |
        let res = the_longest(s1_r, &s2);//            |  | &'y s2
        let res = the_longest(&s2, s1_r);//            |  | &'z s2
        println!("{} is the longest string", res);//   |  |
    }// -----------------------------------------------|  |
}// ------------------------------------------------------|

注意,&'y s2 和 &'z s2 是临时的、传给函数的引用。'z 的有效范围不仅在 the_longest 函数内有效,并且可以延续到函数体之外(因为 the_longest 的生命周期标注)。

而生命周期标注 <'a, 'b: 'a>(s1: &'a str, s2: &'b str) -> &'a str 作为一种契约,规定了以下内容:

  1. 第二个引用至少和第一个引用活得一样长(或者说:第二个引用不能在第一个引用无效之前无效、第二个引用比第一个引用活得更长)
  2. 返回的引用至少和第一个引用活得一样长(或者说:返回的引用不能在第一个引用无效之前无效,这是因为 'a: 'a 恒成立)

那么我猜让你最困惑的点在于,对于 the_longest(s1_r, &s2),似乎不符合上述第一个约定 —— &s2 完全可以并且的确先于第一个引用无效之前无效。

这就涉及再借 (reborrow) 与型变 (variance):

  1. reborrow:通过重新借用来使用那个借用,得到一个更小生命周期的借用。 the_longest(s1_r, &s2) 实际上是 the_longest(&*s1_r, &s2),其中 &*s1_r 的生命周期比 s1_r 更小。
  2. 型变,大多数情况为生命周期的协变 (covariance),核心是将一个更大生命周期的引用(或者含引用的类型)当作更小生命周期的引用(或者含引用的类型)使用。&*s1_r 通过协变,可以被视为和 &s2 一样的生命周期(甚至比 &s2 更小生命周期),从而符合第一个约定。
zylthinking 2023-07-04 12:24

额, 还有个前提, 就是你这代码

fn the_longest<'a, 'b: 'a>(s1: &'a str, s2: &'b str) -> &'a str { if s1.len() > s2.len() { s1 } else { s2 } }

其实编译器知道和下面的比, 除了适用情况更广外, 没有副作用

fn the_longest<'a>(s1: &'a str, s2: &'a str) -> &'a str { if s1.len() > s2.len() { s1 } else { s2 } }

于是实际上, 它就能将上面的代码当作下面的代码来处理

--
👇
zylthinking: 这里有个概念叫做协变, 理解了这个就一切清楚了 https://doc.rust-lang.org/nomicon/subtyping.html

zylthinking 2023-07-04 12:10

这里有个概念叫做协变, 理解了这个就一切清楚了 https://doc.rust-lang.org/nomicon/subtyping.html

1 共 6 条评论, 1 页