很多教程都使用下面的例子来解释生命周期(包括官方的),但我认为它们都没有真正解释幕后发生的事情。
fn x_or_y<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
}
else {
y
}
}
Run Code Online (Sandbox Code Playgroud)
官方教程是这么说的:
这表示 x 和 y 在同一范围内都有效,并且返回值在该范围内也有效。
但为什么x 和 y 都在同一范围内有效?
x如果我在调用此函数后立即销毁但y不理会,那么x寿命会比y.
他们如何在相同的范围内生存?我认为这种说法根本就是错误的。
或者,这是否意味着程序员有责任确保具有相同生命周期说明符的参数必须具有相同的生命周期?
但如果是这样的话,为什么编译器允许不同生命周期的参数具有相同的说明符呢?为什么它不帮我们检查一下?
例如,在下面的代码中,x和y具有不同的生命周期,但我可以给它们两个说明符'a:
// This does NOT compile.
let x = String::from("abcd");
let y = String::from("abc");
let result = x_or_y(x.as_str(), y.as_str());
drop(y); // y's lifetime ends here, which is shorter than that of x.
println!("result = {}", result);
println!("x = {}", x); // x's lifetime ends here, which is longer than that of y.
Run Code Online (Sandbox Code Playgroud)
这会产生以下编译错误,但不是因为生命周期说明符与参数的生命周期不一致,而是因为其他原因:
3 | let y = String::from("abc");
| - binding `y` declared here
4 | let result = x_or_y(x.as_str(), y.as_str());
| - borrow of `y` occurs here
5 | drop(y);
| ^ move out of `y` occurs here
6 | println!("result = {}", result);
| ------ borrow later used here
Run Code Online (Sandbox Code Playgroud)
由于这是编译时,代码还没有运行,所以编译器不知道会返回哪个x和(实际上是因为它的长度较大,因此在运行时不应该有任何错误,因为打印时仍然有效)。yxxresult
然而,编译器确切地知道是y谁造成了问题。
这怎么可能?我认为这只能是因为编译器根据返回值result的生命周期计算出它。
换句话说,result的生命周期只可能与 的生命周期相同y,但不可能相同x,因为只有 y 的生命周期更短并且会导致问题。x会没事的。
换句话说,我们将x和的生命周期指定y为两者'a(尽管它们不同,x但比 长),但编译器实际上将(较短的)y的生命周期分配给返回值,而不是(较长的)的生命周期。yresultx
只有这样编译器才能找出上面所示的错误。
话虽如此,我对生命周期背后机制的猜测是:在指定具有相同生命周期的 2 个参数和返回值后(在本例中),编译器在幕后'a执行以下 2 个步骤:
x获得和的最短生命周期y,这就像 的运算desired_life_time = min(x_lifetime, y_lifetime)。
赋予desired_life_time返回值&'a str。
我想知道这是否正确?
补充:
另一个官方教程说
实际上,这意味着函数返回的引用的生命周期与
longest函数参数引用的值的生命周期中较小的一个相同。
这似乎证实了我上面的猜测,但是生命周期说明符的语法仍然很混乱:
这两个参数使用相同的生命周期说明符进行标记,'a但它们的生命周期不同,一个比另一个小?
如果它们的生命周期不同,为什么我们对它们使用相同的说明符?'a
\n\n\n
x如果我在调用此函数后立即销毁但y不理会,那么x寿命会比y.他们如何在相同的范围内生存?
\n
首先,请注意x_or_y()、x和中y的变量包含引用\xe2\x80\x94\xc2\xa0,但它们不是它们的引用对象。当您想要仔细考虑生命周期时,这是一个重要的区别,因为当我们谈论某些生命周期时,实际上存在三种不同的范围或时间跨度&'a T:
'a,必须不长于Ttype 的特定值在该内存位置中成为有效值并且不被改变的时间。这些都是可能不同的时代。
\n当你借用 a 的内容时String,你会得到一个具有新生命周期的引用&'b str,但'b并不意味着String\xe2\x80\x94\xc2\xa0 存在的整个时期,而是指你借用 this 的String时期time,可以根据需要弯曲更长或更短。'b当您使用引用的时间更长,或者将生命周期限制为与其他生命周期相同或比其他生命周期长时,则被'b限制为更长,并且当您删除、移动或改变String('b必须在此之前结束发生)。
一般来说,当“这个生命周期必须这么长”约束和“这个生命周期必须这么短”约束相互冲突时,就会发生借用错误;如果没有冲突,就没有错误。
\n然而,由于一个特定的原则,许多这些约束可以合作:采用具有较长生命周期的引用并将其视为具有较短生命周期的引用总是合理的,并且 Rust 的类型系统具有 \xe2\x80\x9ccoercion\ xe2\x80\x9d 规则表明这是允许的。(从理论上讲,我们可以说引用在其生命周期内是\xe2\x80\x9c协变的\xe2\x80\x9d。)这就是调用成功的原因x_or_y(x.as_str(), y.as_str());寿命较长的引用被强制具有与寿命较短的引用相同的寿命。
\n\n\xe2\x80\xa6我对生命周期背后机制的猜测是:在指定\n这两个参数和具有相同生命周期的返回值(
\n'a\n这种情况下)之后,编译器在场景后面执行以下两个步骤执行以下两个步骤:\n
\n- \n
\n
x获得和的最短生命周期y,这就像 的操作desired_life_time = min(x_lifetime, y_lifetime)。- \n
赋予
\ndesired_life_time返回值&'a str。
你的总体想法是正确的,但你把它应用到了错误的地方。这并不是说函数的输入生命周期实际上不同,而是beforex_or_y()被调用,在调用它的函数内部,两个引用参数被强制,以便它们的生命周期为min(x_lifetime, y_lifetime)。因此,在函数定义中,'a 实际上只有一个生命周期(每次调用),但对于该函数的这一特定调用,该生命周期'a是min(x_lifetime, y_lifetime)。调整隐式发生在函数调用周围的代码中,而不是在函数内部。
切向的东西;如果没有意义,请忽略它:
\n一般来说,借用检查器是一个约束求解器,因此它甚至不是真正的计算min(x_lifetime, y_lifetime);相反,它对生命周期有一系列限制,例如'a <= 'x && 'a <= 'y,并且看起来这些限制不会相互冲突。只要他们不这样做,它就会很乐意批准您的计划;'a甚至没有真正获得分配给它的具体 \xe2\x80\x9clifetime 值 \xe2\x80\x9d 。一生没有一个真正结束的时刻。仅限制最早结束时间(引用的最后一次使用)和最晚结束(引用对象的删除、移动或突变)。
| 归档时间: |
|
| 查看次数: |
149 次 |
| 最近记录: |