Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >rust中的reborrow和NLL

rust中的reborrow和NLL

作者头像
zy010101
发布于 2023-09-06 05:52:13
发布于 2023-09-06 05:52:13
50200
代码可运行
举报
文章被收录于专栏:程序员程序员
运行总次数:0
代码可运行

reborrow

我们看下面这段代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
fn main() {
    let mut num = 123;
    let ref1 = &mut num;     // 可变引用

    add(ref1);               // 传递给 add 函数

    println!("{}", ref1);    // 再次使用ref1
}

fn add(num: &mut i32) {
    println!("{}", *num);
}

我们知道可变引用是没有实现Copy trait的,因此,当ref1传递给add函数之后,其所有权应该被转移到add函数内,之后应该无法使用ref1,但是上面这段代码是可以编译,运行的。这是为什么呢?

经过辛苦的寻找,在github上找到了相关的pull request以及rust核心开发者nikomatsakis在这篇文档中提到的reborrow。原文如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
One of the less obvious but more important coercions is what I call
*reborrowing*, though it's really a special case of autoborrow. The
idea here is that when we see a parameter of type `&'a T` or `&'a mut
T` we always "reborrow" it, effectively converting to `&'b T` or `&'b
mut T`.  While both are borrowed pointers, the reborrowed version has
a different (generally shorter) lifetime. Let me give an example where
this becomes important:

    fn update(x: &mut int) {
        *x += 1;
    }

    fn update_twice(x: &mut int) {
        update(x);
        update(x);
    }
    
In fact, thanks to auto-borrowing, the second function is implicitly
transformed to:

    fn update_twice(x: &mut int) {
        update(&mut *x);
        update(&mut *x);
    }

This is needed because `&mut` pointers are *affine*, meaning that
otherwise the first call to `update(x)` would move the pointer `x`
into the callee, leading to an error during the second call. The
reborrowing however means that we are in fact not moving `x` but
rather a temporary pointer (let's call it `y`). So long as `y` exists,
access to `x` is disabled, so this is very similar to giving `x` away.
However, lifetime inference will find that the lifetime of this
temporary pointer `y` is limited to the first call to `update` itself,
and so after the call access to `x` will be restored. The borrow
checker rules permit reborrowing under the same conditions in which a
move would be allowed, so this transformation never introduces errors.

对应的译文(来自chatgpt3.5的翻译,非常棒)如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
这段文本描述了Rust中的 "reborrowing" 概念,即重新借用引用的特性,它实际上是 "autoborrow" 的
一种特殊情况。"reborrowing" 的核心思想是,当我们遇到一个类型为 &'a T 或 &'a mut T 的参数时,
我们总是会对它进行 "reborrow",实际上将其转换为 &'b T 或 &'b mut T。虽然这两者都是借用指针,
但 "reborrowed" 版本具有不同(通常更短)的生命周期。
下面通过一个示例来说明 "reborrowing" 为何重要:

fn update(x: &mut i32) {
    *x += 1;
}

fn update_twice(x: &mut i32) {
    update(x);
    update(x);
}
实际上,由于 "auto-borrowing",第二个函数会被隐式转换为:

fn update_twice(x: &mut i32) {
    update(&mut *x);
    update(&mut *x);
}
这是因为 &mut 指针是 "affine" 的,这意味着否则第一次调用 update(x) 会将指针 x 移动到被调用的
函数内部,导致第二次调用时发生错误。但是,"reborrowing" 意味着我们实际上并没有移动 x,而是
移动了一个临时指针(我们称之为 y)。只要 y 存在,对 x 的访问就会被禁用,因此这与将 x 移动
出去非常相似。然而,生命周期推断将发现,临时指针 y 的生命周期仅限于第一次调用 update 本身,
因此在调用后访问 x 将会被恢复。借用检查规则允许在允许移动的情况下进行 "reborrowing",
因此此转换永远不会引入错3误。

综上所述,"reborrowing" 是 Rust 中的一个重要借用模式,它有助于确保代码的安全性和正确性,
同时允许对数据进行操作而不引入潜在的错误。
这种机制是 Rust 借用系统的一部分,有助于编写安全且高效的代码。

总结一下,对于上面的代码而言:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 下面这两行是等价的
add(ref1);
add(&mut *ref1);


// 对于不可变引用而言也是一样的,但是由于不可变引用实现了Copy trait,通常在不可变引用身上不常见。

let num2 = 456;
let ref2 = &num2;

// 下面这两行是等价的
my_print(ref2);
my_print(&*ref2);

fn my_print(num: &i32) {
    println!("{}", num);
}

至于为什么大量的文档和资料没有提到reborrow这个问题,可能得归结于此。在pull request中看到了核心开发者认为正式化reborrow时机不对。

NLL

在Rust的早期版本中,生命周期推断基于词法分析(Lexical analysis),而为了解决这个问题,Rust引入了非词法生命周期(Non-Lexical Lifetime),从而提高了编译器对生命周期的理解和推断。

Rust在1.31版本后提供的NLL(Non-Lexical Lifetime)生命周期简化规则。变量的生命周期跟它的作用域等同,而现在,变量的生命周期结束于它最后一次被使用的位置。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
fn main() {
    let mut s = String::from("hello");

    let r1 = &s;
    let r2 = &s;
    println!("{} and {}", r1, r2);
    // 新编译器中,r1,r2作用域在这里结束

    let r3 = &mut s;
    println!("{}", r3);
}   // 老编译器中,r1、r2、r3作用域在这里结束
    // 新编译器中,r3作用域在这里结束

在现在版本的rust编译器上,上面的代码可以通过正确编译,运行。有了NLL,大大增加了在rust中编写代码的灵活性。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-09-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
悬挂引用是如何被Rust消灭的?
可是,Rust引用并没有堆变量的生杀大权“Ownership”,对于堆变量,只能借来用用,充其量借来改改(再还回去),那么Rust是如何保障引用的权益呢?
袁承兴
2020/08/26
1.3K0
悬挂引用是如何被Rust消灭的?
对 王垠《对 Rust 语言的分析》的分析
当时觉得这篇文章对 Rust 语言的分析太偏颇,但是王垠说这篇文章会一直更新。这几年也有不少新手在群里引用王垠这篇文章对 Rust 的看法,或者直接问我,我原以为过去五年了,王垠应该对文章里对观点有所更新吧,然而并没有。
张汉东
2021/07/14
2.3K2
【Rust每周一知】理解智能指针Box<T>
指针是个通用概念,它表示内存地址这种类型,其引用或“指向”其他数据。Rust中的指针是“第一类公民”(first-class values),可以将它们移动或复制,存储到数据结构中并从函数中返回。Rust提供了多种类型的指针:
MikeLoveRust
2020/02/20
2.2K0
rust引用和借用
如果每次都发生所有权的转移,程序的编写就会变得异常复杂。因此rust和其它编程语言类似,提供了引用的方式来操作。获取变量的引用,称为借用。类似于你借别人的东西来使用,但是这个东西的所有者不是你。引用不会发生所有权的转移。
zy010101
2023/01/02
5410
【Rust学习】05_引用与借用
在这章我们将开始学习Rust的引用和借用,它们是Rust中重要的概念,它们允许我们创建可变引用,以及创建不可变引用。
思索
2024/07/29
1480
【Rust学习】05_引用与借用
【翻译】Rust生命周期常见误区
我曾经有过的所有这些对生命周期的误解,现在有很多初学者也深陷于此。我用到的术语可能不是标准的,所以下面列了一个表格来解释它们的用意。
MikeLoveRust
2020/07/28
1.7K0
Rust编程学习笔记Day6 Borrow的生命周期及约束规则
‍ 昨天(day5)我们发现一个问题:一旦 data 离开了作用域被释放,如果还有引用指向 data,就会造成我们想极力避免的使用已释放内存(use after free)这样的内存安全问题,该怎么办呢?这就引出了我们今天的主角。
用户1072003
2023/02/23
3830
Rust编程学习笔记Day6 Borrow的生命周期及约束规则
Rust 让人迷惑的 “借用”
本篇尽量深入浅出,不想学 Rust 的也可以读读,多种语言对比很有很大的收获,Go 再好也不是所有场景通吃^_^
MikeLoveRust
2021/07/16
4770
Rust 让人迷惑的 “借用”
Rust 提升安全性的方式
Rust 1 是 Mozilla 公司开发的编程语言,它在 2010 才开始发布第一个版本,可以说是一个非常年轻的语言了。在提出一个新的编程语言的时候,设计者必须要回答的一个问题是「为什么要设计这样一个编程语言?」。对于 Rust 来说,他的目的就是要在保证安全的基础上不失对底层的控制力。
zhiruili
2021/08/10
9950
Rust学习笔记之所有权
所有权ownership可以说Rust中最为独特的一个功能,正是所有权概念和相关工具的引入,Rust才能够「在没有垃圾回收机制的前提下保障内存安全」。
前端柒八九
2023/03/23
6310
Rust学习笔记之所有权
rust闭包(Closure)
闭包在现代化的编程语言中普遍存在。闭包是一种匿名函数,它可以赋值给变量也可以作为参数传递给其它函数,不同于函数的是,它允许捕获调用者作用域中的值。Rust 闭包在形式上借鉴了 Smalltalk 和 Ruby 语言,与函数最大的不同就是它的参数是通过 |parm1| 的形式进行声明,如果是多个参数就 |param1, param2,…|, 下面给出闭包的形式定义:
zy010101
2023/04/27
6910
rust闭包(Closure)
一名Java开发的Rust学习笔记
笔者的主力语言是Java,近三年Kotlin、Groovy、Go、TypeScript写得比较多。早年间还写过一些Python和JavaScript。总得来说落地在生产中的语言都是应用级语言,对于系统编程级语言接触不多。但这不妨碍我写下这么一篇笔记,说不定也有一些常年在应用层的同学想领略一下Rust的风采呢。
泊浮目
2024/03/19
2610
一名Java开发的Rust学习笔记
【Rust】004-Rust 所有权
想象一下,栈就像一叠盘子。先来的盘子在底部,新盘子则放在顶部。取用时,总是先取最上面的盘子,就像在餐馆里洗完的盘子先用先拿。这就是先进后出的原则。但是,盘子的大小必须是确定的,否则无法存放在这个“栈”里。
訾博ZiBo
2025/01/06
920
【Rust每周一知】如何理解Rust中的可变与不可变?
Rust的所有权(ownership)机制规定:Rust中的每个值都有一个被称为其所有者(owner)的变量,并且有且只能有唯一的所有者。
MikeLoveRust
2019/12/25
2.1K0
go 开发者的 rust 入门
即:在任意给定时间,要么 只能有一个可变引用,要么 只能有多个不可变引用。引用必须总是有效的。
王磊-字节跳动
2021/11/27
1.9K0
【Rust】001-基础语法:变量声明及数据类型
Rust 入门与实践:https://juejin.cn/book/7269676791348854839?utm_source=course_list
訾博ZiBo
2025/01/06
720
【Rust】001-基础语法:变量声明及数据类型
rust-生命周期
先说大白话,rust 的生命周期标注,是为了明确多个变量的生命周期是否一致,仅此而已,因为如果rust不知道多个变量的生命周期是否一致,它无法确的知道这个变量是否已经被释放。这个下面再细说,先说有什么用。
潇洒
2023/10/23
2320
Rust入门之严谨如你
Rust作为一门快速发展的编程语言,已经在很多知名项目中使用,如firecracker、libra、tikv,包括Windows和Linux都在考虑Rust【1】。其中很重要的因素便是它的安全性和性能,这方面特性使Rust非常适合于系统编程。
Radar3
2020/11/25
1.8K2
《Rust for Rustaceans》 样章试译 | 第二章 Rust 基础
本文是对 Jon Gjengset 写的新书 《Rust for Rustaceans》样章第二章的中文试译初稿。出于对 Jon 的尊敬,以及想了解 Jon 眼中的 Rust ,我打算翻译一下这本书。发出来让大家看看翻译效果,欢迎指正。
张汉东
2021/07/14
6K1
一文带你走进 Rust 和 WebAssembly 的世界
在进行正式的分享之前,先来说一说为什么,要学习 Rust 这一门在广义上归属于后端的语言,以及它能带给我们什么,未来有什么前景。
童欧巴
2021/08/20
2.2K0
一文带你走进 Rust 和 WebAssembly 的世界
相关推荐
悬挂引用是如何被Rust消灭的?
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验