为什么链接生命周期仅与可变引用相关?

2024-05-04

前几天,有一个问题 https://stackoverflow.com/questions/32089410/lifetimes-and-references-to-objects-containing-references有人对包含借用数据本身的类型的可变引用的链接生命周期有问题。问题在于提供对类型的引用,并借用与类型内借用的数据具有相同生命周期的借用。 我尝试重现问题:

struct VecRef<'a>(&'a Vec<u8>);

struct VecRefRef<'a>(&'a mut VecRef<'a>);

fn main() {
    let v = vec![8u8, 9, 10];
    let mut ref_v = VecRef(&v);
    create(&mut ref_v);
}

fn create<'b, 'a>(r: &'b mut VecRef<'a>) {
    VecRefRef(r);
}

我明确注释了'b在此处create()。这不会编译:

error[E0623]: lifetime mismatch
  --> src/main.rs:12:15
   |
11 | fn create<'b, 'a>(r: &'b mut VecRef<'a>) {
   |                      ------------------
   |                      |
   |                      these two types are declared with different lifetimes...
12 |     VecRefRef(r);
   |               ^ ...but data from `r` flows into `r` here

一生'b是这样的'b < 'a因此违反了约束VecRefRef<'a>与所提到的具有完全相同的寿命VecRef<'a>.

我将可变引用的生命周期与借用的数据链接起来VecRef<'a>:

fn create<'a>(r: &'a mut VecRef<'a>) {
    VecRefRef(r);
}

现在可以了。但为什么?我怎么能提供这样的参考呢?可变引用r inside create()的寿命为VecRef<'a> not 'a。为什么问题没有被推到函数的调用方create()?

我注意到另一件事我不明白。如果我使用不可变的里面的参考VecRefRef<'a>struct,当提供具有不同生命周期的引用时,它在某种程度上不再重要了'a:

struct VecRef<'a>(&'a Vec<u8>);

struct VecRefRef<'a>(&'a VecRef<'a>); // now an immutable reference

fn main() {
    let v = vec![8u8, 9, 10];
    let mut ref_v = VecRef(&v);
    create(&mut ref_v);
}

fn create<'b, 'a>(r: &'b mut VecRef<'a>) {
    VecRefRef(r);
}

这与第一个示例相反,其中VecRefRef<'a>对 a 进行了可变引用VecRef<'a>。我知道可变引用有不同的别名规则(根本没有别名),但这与这里的链接生命周期有什么关系?


Warning:我是从我并不真正具备的专业水平上来说的。考虑到这篇文章的长度,我可能错了很多次。

TL;DR:顶级值的生命周期是协变的。引用值的生命周期是不变的。

引入问题

您可以通过替换显着简化您的示例VecRef<'a> with &'a mut T.

此外,应该删除main,因为谈论更完整general函数的行为而不是某些特定的生命周期实例化。

代替VecRefRef的构造函数,让我们使用这个函数:

fn use_same_ref_ref<'c>(reference: &'c mut &'c mut ()) {}

在我们进一步讨论之前,了解生命周期如何在 Rust 中隐式转换非常重要。当一个指针分配给另一个显式注释的名称时,就会发生生命周期强制。这允许的最明显的事情是缩短顶级指针的生命周期。因此,这不是一个典型的举动。

Aside:我说“明确注释”是因为在隐式情况下,例如let x = y or fn f<T>(_: T) {},重借似乎没有发生 https://stackoverflow.com/questions/32154387/why-is-the-mutable-reference-not-moved-here/32158223#32158223。目前尚不清楚这是否是有意为之。

完整的例子是

fn use_same_ref_ref<'c>(reference: &'c mut &'c mut ()) {}

fn use_ref_ref<'a, 'b>(reference: &'a mut &'b mut ()) {
    use_same_ref_ref(reference);
}

这给出了同样的错误:

error[E0623]: lifetime mismatch
 --> src/main.rs:5:26
  |
4 |     fn use_ref_ref<'a, 'b>(reference: &'a mut &'b mut ()) {
  |                                       ------------------
  |                                       |
  |                                       these two types are declared with different lifetimes...
5 |         use_same_ref_ref(reference);
  |                          ^^^^^^^^^ ...but data from `reference` flows into `reference` here

一个简单的修复

人们可以通过这样做来解决它

fn use_same_ref_ref<'c>(reference: &'c mut &'c mut ()) {}

fn use_ref_ref<'a>(reference: &'a mut &'a mut ()) {
    use_same_ref_ref(reference);
}

因为现在签名在逻辑上是相同的。然而,不明显的是为什么

let mut val = ();
let mut reference = &mut val;
let ref_ref = &mut reference;

use_ref_ref(ref_ref);

能够产生一个&'a mut &'a mut ().

一个不那么简单的修复

人们可以改为强制执行'a: 'b

fn use_same_ref_ref<'c>(reference: &'c mut &'c mut ()) {}

fn use_ref_ref<'a: 'b, 'b>(reference: &'a mut &'b mut ()) {
    use_same_ref_ref(reference);
}

这意味着外部引用的生命周期是at least与内部生命周期一样大。

并不明显

  • why &'a mut &'b mut ()不可投射到&'c mut &'c mut (), or

  • 这是否比&'a mut &'a mut ().

我希望能够回答这些问题。

一个非修复

断言'b: 'a并不能解决问题。

fn use_same_ref_ref<'c>(reference: &'c mut &'c mut ()) {}

fn use_ref_ref<'a, 'b: 'a>(reference: &'a mut &'b mut ()) {
    use_same_ref_ref(reference);
}

另一个更令人惊讶的修复

使外部引用不可变可以解决问题

fn use_same_ref_ref<'c>(reference: &'c &'c mut ()) {}

fn use_ref_ref<'a, 'b>(reference: &'a &'b mut ()) {
    use_same_ref_ref(reference);
}

还有更令人惊讶的未修复!

制作inner引用不可变根本没有帮助!

fn use_same_ref_ref<'c>(reference: &'c mut &'c ()) {}

fn use_ref_ref<'a, 'b>(reference: &'a mut &'b ()) {
    use_same_ref_ref(reference);
}

但为什么??!

原因是...

等等,首先我们讨论方差

计算机科学中两个非常重要的概念是协方差 and 逆变。我不会使用这些名称(我会非常明确地说明我的铸造方式),但这些名称对于搜索互联网 https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science).

在理解这里的行为之前,理解方差的概念非常重要。如果您学过涵盖此内容的大学课程,或者您可以从其他上下文中记住它,那么您就处于有利地位。不过,您可能仍然会感激将这个想法与生命周期联系起来的帮助。

简单的情况 - 一个普通的指针

考虑一些带有指针的堆栈位置:

    ║ Name      │ Type                │ Value
 ───╫───────────┼─────────────────────┼───────
  1 ║ val       │ i32                 │ -1
 ───╫───────────┼─────────────────────┼───────
  2 ║ reference │ &'x mut i32         │ 0x1

堆栈向下增长,因此reference堆栈位置是在之后创建的val,并且将在之前删除val is.

考虑一下你这样做

let new_ref = reference;

to get

    ║ Name      │ Type        │ Value  
 ───╫───────────┼─────────────┼─────── 
  1 ║ val       │ i32         │ -1     
 ───╫───────────┼─────────────┼─────── 
  2 ║ reference │ &'x mut i32 │ 0x1    
 ───╫───────────┼─────────────┼─────── 
  3 ║ new_ref   │ &'y mut i32 │ 0x1    

什么生命周期有效'y?

考虑两个可变指针操作:

  • Read
  • Write

Read防止'y从成长中,因为'x引用仅保证对象在范围内保持活动状态'x。然而,read并不能阻止'y避免收缩,因为当指向的值处于活动状态时进行任何读取都将产生一个与生命周期无关的值'y.

Write防止'y也不会增长,因为无法写入无效的指针。然而,write并不能阻止'y避免收缩,因为对指针的任何写入都会复制其中的值,这使其独立于生命周期'y.

硬壳——指针指针

考虑一些带有指针的堆栈位置:

    ║ Name      │ Type                │ Value  
 ───╫───────────┼─────────────────────┼─────── 
  1 ║ val       │ i32                 │ -1     
 ───╫───────────┼─────────────────────┼─────── 
  2 ║ reference │ &'a mut i32         │ 0x1    
 ───╫───────────┼─────────────────────┼─────── 
  3 ║ ref_ref   │ &'x mut &'a mut i32 │ 0x2    

考虑一下你这样做

let new_ref_ref = ref_ref;

to get

    ║ Name        │ Type                │ Value  
 ───╫─────────────┼─────────────────────┼─────── 
  1 ║ val         │ i32                 │ -1     
 ───╫─────────────┼─────────────────────┼─────── 
  2 ║ reference   │ &'a mut i32         │ 0x1    
 ───╫─────────────┼─────────────────────┼─────── 
  3 ║ ref_ref     │ &'x mut &'a mut i32 │ 0x2    
 ───╫─────────────┼─────────────────────┼─────── 
  4 ║ new_ref_ref │ &'y mut &'b mut i32 │ 0x2    

现在有两个问题:

  1. 什么生命周期有效'y?

  2. 什么生命周期有效'b?

我们首先考虑'y与两个可变指针操作:

  • Read
  • Write

Read防止'y从成长中,因为'x引用仅保证对象在范围内保持活动状态'x。然而,read并不能阻止'y避免收缩,因为当指向的值处于活动状态时进行任何读取都将产生一个与生命周期无关的值'y.

Write防止'y也不会增长,因为无法写入无效的指针。然而,write并不能阻止'y避免收缩,因为对指针的任何写入都会复制其中的值,这使其独立于生命周期'y.

这和以前一样。

现在,考虑'b与两个可变指针操作

Read防止'b增长,因为如果要从外部指针中提取内部指针,您将能够在之后读取它'a已过期。

Write防止'b也从增长中,因为如果要从外部指针中提取内部指针,您将能够在之后写入它'a已过期。

Read and write一起还可以预防'b由于这种情况,导致收缩:

let ref_ref: &'x mut &'a mut i32 = ...;

{
    // Has lifetime 'b, which is smaller than 'a
    let new_val: i32 = 123;

    // Shrink 'a to 'b
    let new_ref_ref: &'x mut &'b mut i32 = ref_ref;

    *new_ref_ref = &mut new_val;
}

// new_ref_ref is out of scope, so ref_ref is usable again
let ref_ref: &'a mut i32 = *ref_ref;
// Oops, we have an &'a mut i32 pointer to a dropped value!

Ergo, 'b不能缩小,也不能增长'a, so 'a == 'b确切地。这意味着&'y mut &'b mut i32在生命周期中是不变的 'b.

好的,这能解决我们的疑问吗?

还记得代码吗?

fn use_same_ref_ref<'c>(reference: &'c mut &'c mut ()) {}

fn use_ref_ref<'a, 'b>(reference: &'a mut &'b mut ()) {
    use_same_ref_ref(reference);
}

你打电话时use_same_ref_ref,尝试进行强制转换

&'a mut &'b mut ()  →  &'c mut &'c mut ()

现在请注意'b == 'c因为我们讨论了方差。因此我们实际上是在铸造

&'a mut &'b mut ()  →  &'b mut &'b mut ()

外层&'a只能缩小。为了做到这一点,编译器需要知道

'a: 'b

编译器不知道这一点,因此编译失败。

我们的其他例子呢?

第一个是

fn use_same_ref_ref<'c>(reference: &'c mut &'c mut ()) {}

fn use_ref_ref<'a>(reference: &'a mut &'a mut ()) {
    use_same_ref_ref(reference);
}

代替'a: 'b,编译器现在需要'a: 'a,这确实是事实。

第二个直接断言'a: 'b

fn use_same_ref_ref<'c>(reference: &'c mut &'c mut ()) {}

fn use_ref_ref<'a: 'b, 'b>(reference: &'a mut &'b mut ()) {
    use_same_ref_ref(reference);
}

第三个断言'b: 'a

fn use_same_ref_ref<'c>(reference: &'c mut &'c mut ()) {}

fn use_ref_ref<'a, 'b: 'a>(reference: &'a mut &'b mut ()) {
    use_same_ref_ref(reference);
}

这不起作用,因为这不是所需的断言。

不变性怎么样?

我们这里有两个案例。第一个是使外部引用不可变。

fn use_same_ref_ref<'c>(reference: &'c &'c mut ()) {}

fn use_ref_ref<'a, 'b>(reference: &'a &'b mut ()) {
    use_same_ref_ref(reference);
}

这个有效。为什么?

好吧,考虑一下我们的收缩问题&'b从以前:

Read and write一起还可以预防'b由于这种情况,导致收缩:

let ref_ref: &'x mut &'a mut i32 = ...;

{
    // Has lifetime 'b, which is smaller than 'a
    let new_val: i32 = 123;

    // Shrink 'a to 'b
    let new_ref_ref: &'x mut &'b mut i32 = ref_ref;

    *new_ref_ref = &mut new_val;
}

// new_ref_ref is out of scope, so ref_ref is usable again
let ref_ref: &'a mut i32 = *ref_ref;
// Oops, we have an &'a mut i32 pointer to a dropped value!

Ergo, 'b不能缩小,也不能增长'a, so 'a == 'b确切地。

之所以会发生这种情况,是因为我们可以将内部引用替换为一些新的、寿命不够长的引用。如果我们无法交换参考,这不是问题。因此,缩短内部引用的寿命是可能的。

那失败者呢?

使内部引用不可变并没有帮助:

fn use_same_ref_ref<'c>(reference: &'c mut &'c ()) {}

fn use_ref_ref<'a, 'b>(reference: &'a mut &'b ()) {
    use_same_ref_ref(reference);
}

当您考虑到之前提到的问题从不涉及任何内部引用的读取时,这是有道理的。事实上,这里有问题的代码经过修改以证明:

let ref_ref: &'x mut &'a i32 = ...;

{
    // Has lifetime 'b, which is smaller than 'a
    let new_val: i32 = 123;

    // Shrink 'a to 'b
    let new_ref_ref: &'x mut &'b i32 = ref_ref;

    *new_ref_ref = &new_val;
}

// new_ref_ref is out of scope, so ref_ref is usable again
let ref_ref: &'a i32 = *ref_ref;
// Oops, we have an &'a i32 pointer to a dropped value!

还有一个问题

虽然已经过去很久了,但回想一下:

人们可以改为强制执行'a: 'b

fn use_same_ref_ref<'c>(reference: &'c mut &'c mut ()) {}

fn use_ref_ref<'a: 'b, 'b>(reference: &'a mut &'b mut ()) {
    use_same_ref_ref(reference);
}

这意味着外部引用的生命周期是at least与内部生命周期一样大。

并不明显

  • why &'a mut &'b mut ()不可投射到&'c mut &'c mut (), or

  • 这是否比&'a mut &'a mut ().

我希望能够回答这些问题。

我们已经回答了第一个要点问题,那么第二个问题呢?做'a: 'b允许超过'a == 'b?

考虑一些具有类型的调用者&'x mut &'y mut (). If 'x : 'y,那么它会自动转换为&'y mut &'y mut ()。相反,如果'x == 'y, then 'x : 'y已经成立了!因此,只有当您希望返回包含以下内容的类型时,差异才重要:'x对于呼叫者来说,他是唯一能够区分这两者的人。由于这里不是这种情况,因此两者是等效的。

还有一件事

如果你写

let mut val = ();
let mut reference = &mut val;
let ref_ref = &mut reference;

use_ref_ref(ref_ref);

where use_ref_ref被定义为

fn use_ref_ref<'a: 'b, 'b>(reference: &'a mut &'b mut ()) {
    use_same_ref_ref(reference);
}

代码如何能够执行'a: 'b?经检查,情况似乎恰恰相反!

嗯,记住这一点

let reference = &mut val;

能够缩短其生命周期,因为此时它是外部生命周期。因此,它可以指一生smaller比实际寿命val,即使指针超出了该生命周期!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么链接生命周期仅与可变引用相关? 的相关文章

随机推荐

  • 更改流程布局的 itemSize 后单元格大小未更新

    在我的应用程序中 我有一个全屏分页集合视图 每个单元格也需要全屏 因此集合视图布局的项目大小需要与视图控制器的视图边界大小相同 为此 在viewDidLayoutSubviews我只是设置了项目大小 它就按预期工作了 当我呈现这个屏幕时 v
  • ionic 2 从 json 填充选择选项

    我正在尝试动态填充ion select带有 json 对象的下拉列表 我的 html 组件如下所示
  • Active Directory:获取用户所属的组

    我想找到用户所属的组列表 我尝试了几种解决方案http www codeproject com KB system everythingInAD aspx http www codeproject com KB system everyth
  • 如何在java hashset中查找并返回对象

    根据 HashSet javadoc HashSet contains 仅返回布尔值 如何在 hashSet 中 查找 对象并修改它 它不是原始数据类型 我看到 HashTable 有一个 get 方法 但我更喜欢使用该集合 您可以删除一个
  • 将 POSIXct 日期值更改为每周的第一天

    我想计算平均值Dist每周使用这些数据 如下 同时保留使用POSIXct时间课 df lt structure list IndID structure c 1L 1L 1L 1L 1L 1L 1L 1L 1L 1L 1L 1L 1L 1L
  • 如何将一个窗格连接到另一个窗格

    如何将输出连接到paneWithList PaneWithList其上有一个监听器JList以便将所选行输出到控制台 我怎样才能将该输出定向到JTextPane关于输出 Could PaneWithList触发一个事件Main拿起 会属性更
  • 使用 RSelenium 下载嵌入到框架中的文件

    我正在参与一个项目 其中有一个网页 我需要单击该网页才能获取 pdf 文件 该文件出现在同一页面内的新窗口中 我认为是 iframe 然后我需要单击一个按钮来下载文件 我正在使用的代码如下 library wdman library RSe
  • Firebase Auth:如何检测 Firebase 尝试自动登录当前用户?

    我在用着firebase auth onAuthStateChanged 在浏览器中检测用户是否已登录 并在每个页面上加载 firebase 尝试登录当前用户 可能基于 IndexDB 中存储的令牌 我需要检测 firebase 何时尝试自
  • 与 ssh2_connect() 断开连接

    我已经使用 ssh2 连接ssh2 connect到服务器 但我没有看到任何方法在联机帮助页中 http php net ssh2 connect我应该如何结束连接 我不太喜欢在断开连接之前等待脚本结束 我可以用吗fclose 这听起来不对
  • 淡化背景但不淡化文本

    我已经对 div 应用了 CSS 淡入 淡出解决方案 在很大程度上我对此感到满意 但是我只想将其应用于背景 而不是文本 我需要文本始终完全不透明 参见示例 http jsfiddle net howiepaul gUAPV 33 http
  • bash 调整图像尺寸以适合特定大小

    我到处搜索但找不到这个问题的答案 我想精确输出一个文件夹中的所有图像 大小为 50Kb 并保持原始的宽高比 I tried ImageMagick并将大小调整为 250x250 例如 但它对我不起作用 它所做的是更改第一个尺寸并适应另一个尺
  • 在 R 中使用 NA 计算栅格数据的变异函数

    Summary 我有一个包含 NA 值的栅格数据集 并且想要计算它的变异函数 忽略 NA 我怎样才能做到这一点 我有一个图像 已使用以下命令加载到 R 中readGDAL函数 存储为im 为了使其可重复 结果dput图像上可在https g
  • 有用的库存 SQL 数据集吗?

    有谁知道有哪些资源可以提供优质 有用的股票数据集 例如 我下载了一个包含美国所有州 城市和邮政编码的 SQL 脚本 这在最近的一个应用程序中节省了我很多时间 我希望能够按地理位置进行查找 你们中有人知道其他可以免费下载的有用数据集吗 例如
  • Struts 未处理的异常 - 没有为操作定义结果 - Struts Spring 和 hibernate 集成

    实际上 我正在致力于在在线考试项目上实现 Struts Spring 和 Hibernate 集成 但是当我在 JSP 页面中提交值时 它会抛出以下错误 Struts 问题报告 Struts has detected an unhandle
  • 配置错误:无法链接到 boost_system

    我正在尝试在 Debian 上安装一个软件包 足球模拟器 2d 当我进入目录并运行时 configure 我得到以下信息 reza debian soccer rcssserver 15 0 1 configure checking for
  • 显示不同表中的名称而不是 ID

    我有 2 张桌子 Category带主键ID和列Name Employee带主键ID和列Category id Note Category id现在显示ID正确地 我想展示Name代替ID对于输出Employee Attempt categ
  • 将 CassandraUnit 与 Astyanax 结合使用时出现依赖性问题

    我有一个 SessionDaoCassandraImpl 类 它使用 Astyanax 从 Cassandra 读取数据 我想使用嵌入式 Cassandra 服务器进行测试 卡桑德拉单元 https github com jsevellec
  • ServiceStack 验证并不总是触发

    因此 我尝试使用 RavenDB 和 ServiceStack 构建端到端集成测试套件 但遇到了一个非常奇怪的问题 即验证无法对某些请求运行 这真的很奇怪 我不确定我做错了什么 我正在使用 NCrunch 有时测试通过 有时失败 希望这是一
  • Pyside QPushButton 和 matplotlib 的连接

    我正在尝试使用 matplotlib 开发一个非常简单的 pyside Qt 程序 我希望按下按钮时绘制图表 到目前为止 我可以在构造函数上绘制一些东西 但无法将 Pyside 事件与 matplotlib 连接起来 有没有办法做到这一点
  • 为什么链接生命周期仅与可变引用相关?

    前几天 有一个问题 https stackoverflow com questions 32089410 lifetimes and references to objects containing references有人对包含借用数据本