gpt4 book ai didi

rust - 为什么 Rust 中的通用生命周期参数可以专门用于一个对象的两个不相交的生命周期?

转载 作者:行者123 更新时间:2023-12-04 11:28:13 28 4
gpt4 key购买 nike

在下面的一段代码中,我试图了解通用生命周期参数 'a是专业的。

struct Wrapper<'a>(&'a i32);

fn foo() {
let mut r;

{
let x = 0; // the lifetime of x, call it 'x, starts from here |
r = Wrapper(&x); // 'a parameter in Wrapper is specialized to 'x |
drop(r); // |
} // --------------------------------- 'x ends here |

{
let y = 0; // the lifetime of y, call it 'y, starts from here |
// 'y is distinct from 'x |
// neither outlives the other one |
r = Wrapper(&y); // why 'a parameter can again be specialized to 'y? |
drop(r); // |
} // ------------------------------------ 'y ends here |
}
为什么可能是通用生命周期参数 'a可以专门用于两个不相交的生命周期 'x'y , 一个对象 'r ?
从另一个角度来看,我对 r的具体类型很困惑。 .来自 subtyping and variance Rustonomicon 一章,我明白生命周期 'a是泛型类型 Wrapper<'a> 的一部分.当泛型被专门化时,它不能是 Wrapper<'x>也不是 Wrapper<'y> .那么 r的类型是什么? ?
也许 higher-rank trait bound有什么关系吗?
如果有人能解释 Rust 编译器如何在内部解释这一点,我将不胜感激。
更新:
现有答案表明 r 的生命周期可以有多个起点和终点。如果这是真的,那么 'r'x 的并集和 'y .然而,这种解释与 subtyping 不太吻合。规则。例如,考虑以下代码, 'r2也不是 'r是另一个的子类型(或生命周期更长),所以我们不应该调用 bar(r, r2) ,但我们实际上可以。矛盾。
struct Wrapper<'a>(&'a i32);

fn bar<'a>(_p: Wrapper<'a>, _q: Wrapper<'a>) {}

fn foo() {
let mut r;

{
let z = 0;
let r2 = Wrapper(&z); // -> |
{ // |
let x = 0; // -> | // +--'r2
r = Wrapper(&x); // |--'x--+ // |
bar(r, r2); // | | // <- |
} // <- | |
} // |
{ // +--'r
let y = 0; // -> | |
r = Wrapper(&y); // |--'y--+
drop(r); // |
} // <- |
}

最佳答案

你在以错误的方式思考人生。 r的生命周期没有被专门研究 'x也不是 'y .它有自己的生命周期(我们称之为 'r )。
Non-Lexical Lifetimes RFC 中所述,值的生命周期(或 RFC 所称的范围)是程序中该值在 future 仍可使用的点。至关重要的是,生命周期不一定是线性的,也不必适合特定的语法范围(即 {} 块)。此外,生命周期不连接到变量(如 r ),而是它们绑定(bind)到的值。因此,如果您分配给一个变量(即 r = .. ),您实际上会杀死一个值并开始另一个值。它不算作使用。但是,分配给值的成员(即 r.0 = .. )是一种用法,因为您正在更改现有值的一小部分。
在您的情况下,'r有两个起点和两个终点。第一个起点是r = Wrapper(&x);第一个端点位于第一个 drop(r) .第二个起点和终点在 y 块中。可视化:

struct Wrapper<'a>(&'a i32);
fn foo() {
let mut r;

{
let x = 0;
r = Wrapper(&x); // -> |
// |--'x--+
drop(r); // <- | |
} // |
{ // +--'r
let y = 0; // |
r = Wrapper(&y); // -> | |
// |--'y--+
drop(r); // <- |
}
}
这里我也插入了 'x'y以供引用。注意如何 'r特别是不在两个范围之间。
当借用检查时,生命周期的要求是生命周期需要比彼此更长。假设你有界限 'a: 'b ( 'a 必须比 'b 生命周期长)。对于此绑定(bind),任何地方 'b正在直播, 'a也必须直播。这就是outlives的意思。另一种说法: 'a lasts longer than 'b .
但是,在这里我们还必须考虑到位置感知的借用检查。这意味着 'a only needs to outlive 'b starting from the point their relation begins.
所以让我们看看我们的例子。对于类似 r = Wrapper(&'x x) 的声明(我添加了生命周期以供引用)借用检查器必须确保引用的生命周期 &'x xr 的生命周期长(即 'x: 'r )从 r = Wrapper(&x) 开始.查看上面的生命周期,我们可以看到任何地方 'x已上线,也是 'r . 'x当范围结束时死亡,但幸运的是, 'r 也是如此.所以这个检查。冲洗并重复 'y .

为了对此更有信心,让我们看一下示例的一些变体,看看我们如何预测编译器是接受还是拒绝它。
示例 1
我们将简单地删除 drop打电话看看它是否有所作为:
struct Wrapper<'a>(&'a i32);
fn foo() {
let mut r;

{
let x = 0;
r = Wrapper(&x);
}

{
let y = 0;
r = Wrapper(&y);
}
}
这不会改变任何生命周期。请记住,生命周期与将来是否会使用值有关。由于我们还没有添加 r 的任何用途, x , 或 y ,没有生命周期改变。
正如预期的那样, it compiles just fine .
示例 2
如果 x怎么办?不在范围内:
struct Wrapper<'a>(&'a i32);
fn foo() {
let mut r;

let x = 0;
r = Wrapper(&x);

{
let y = 0;
r = Wrapper(&y);
}
}
这里 'r'x已移入父作用域,但除此之外它们都与原始示例非常相似。同样与 'y .因此所有的界限仍然成立,并且 it compiles . y 也是一样的.
示例 3
所以让我们改变 r 的一些用途.如果我们不创建一个新的 Wrapper 会怎样?为 y ,我们只需覆盖引用:
struct Wrapper<'a>(&'a i32);
fn foo() {
let mut r;
{
let x = 0;
r = Wrapper(&x);
}
{
let y = 0;
r.0 = &y;
}
}
这将发生重大变化 'r ,因为在第一个作用域之后,它将在第二个作用域中使用以分配一个新的引用。因此, 'r现在跨越 r = Wrapper(&x)r.0 = &y .然而, 'x无法匹配,如 x活得不够长(超出范围)。因此,我们应该得到一个错误消息 x活得不够久, which we do .
示例 4
如果我们只使用 r 呢?在两个范围之后:
#[derive(Debug)]
struct Wrapper<'a>(&'a i32);
fn foo() {
let mut r;
{
let x = 0;
r = Wrapper(&x);
}
{
let y = 0;
r = Wrapper(&y);
}
println!("{:?}", r)
}
在这里, 'r现在比原始示例稍大,在 r = Wrapper(&y) 之后继续到达打印品。但是,它仍然不在两个块之间,因为在第一个范围中分配的值从未在第二个范围中使用过。因此, 'x活下去 'r .然而, 'y现在需要足够大以达到打印(因为 'r 这样做),它不能,因为 y将超出范围。因此,编译器应该提示 y活得不够久,但不说 x .幸运的是, that is the case .

关于子类型
问题的更新询问为什么上述似乎与子类型规则相矛盾。给定一个函数 fn some_fn<'a>(a: &'a i32, b: &'a i32) ,为什么我们可以传递两个生命周期不同的这个函数的引用。似乎函数声明在两种情况下都需要完全相同的生命周期。
答案是子类型有 specific rules for lifetimes :

Whenever references are copied from one location to another, the Rust subtyping rules require that the lifetime of the source reference outlives the lifetime of the target location.


这意味着 Rust 永远不会检查两个生命周期是否相等。它只检查它们是否以正确的方式彼此存活。在更新的示例中, 'a是对 bar 的调用的生命周期.路过时 rr2到它,我们得到了边界 'r: 'a'r2: 'a .这使它变得非常容易,因为 'a只在通话期间活着,其他的生命也都活着。在调用之后,所有三个生命周期都已死亡,因此绑定(bind)变得微不足道。

关于rust - 为什么 Rust 中的通用生命周期参数可以专门用于一个对象的两个不相交的生命周期?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67632034/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com