009、引用

1. 引用与借用

        下面的示例重新定义了一个新的 calculate_length 函数。与之前不同的是,新的函数签名使用了 String 的引用作为参数而没有直接转移值的所有权:

fn main() { 
    let s1 = String::from("hello"); 
 
    let len = calculate_length(&s1); 
 
    println!("The length of '{}' is {}.", s1, len); 
} 
 
fn calculate_length(s: &String) -> usize { 
    s.len() 
} 

        首先需要注意的是,变量声明及函数返回值中的那些元组代码都消失了。其次,我们在调用 calculate_length 函数时使用了 &s1 作为参数,且在该函数的定义中,我们使用 &String 替代了 String

        这些 & 代表的就是引用语义,它们允许你在不获取所有权的前提下使用值。下图所展示的是该过程的一个图解。 

图1:&String s指向String s1的图解

注意

        与使用 & 进行引用相反的操作被称为解引用(dereferencing),它使用 * 作为运算符。这个我们后面文章中会详细讨论。

        现在,让我们仔细观察一下这个函数的调用过程:

let s1 = String::from("hello");

let len = calculate_length(&s1);

        这里的 &s1 语法允许我们在不转移所有权的前提下,创建一个指向 s1 值的引用。由于引用不持有值的所有权,所以当引用离开当前作用域时,它指向的值也不会被丢弃。

        同理,函数签名中的 & 用来表明参数 s 的类型是一个引用。下面的注释给出了更详细的解释: 

fn calculate_length(s: &String) -> usize { // s 是一个指向 String 的引用
    s.len()
} // 到这里,s离开作用域。但是由于它并不持有自己所指向值的所有权,
//所以没有什么特殊的事情会发生

        此处,变量 s 的有效作用域与其他任何函数参数一样,唯一不同的是,它不会在离开自己的作用域时销毁其指向的数据,因为它并不拥有该数据的所有权。

        当一个函数使用引用而不是值本身作为参数时,我们便不需要为了归还所有权而特意去返回值,毕竟在这种情况下,我们根本没有取得所有权。这种通过引用传递参数给函数的方法也被称为借用(borrowing)。

        在现实生活中,假如一个人拥有某件东西,你可以从他那里把东西借过来。但是当你使用完毕时,就必须将东西还回去。如果我们尝试着修改借用的值又会发生什么呢?我们来运行下面的代码测试一下。剧透:这段代码无法通过编译! 

fn main() { 
    let s = String::from("hello"); 
 
    change(&s); 
} 
 
fn change(some_string: &String) { 
    some_string.push_str(", world"); 
} 

        报错内容如下:

error[E0596]: cannot borrow immutable borrowed content
`*some_string` as mutable
 --> error.rs:8:5
  |
7 | fn change(some_string: &String) {
  |                        ------- use `&mut String` here to make mutable
8 |     some_string.push_str(", world");
  |     ^^^^^^^^^^^ cannot borrow as mutable

        与变量类似,引用是默认不可变的,Rust不允许我们去修改引用指向的值。 

 

2. 可变引用

        我们可以通过进行一个小小的调整来修复上面代码中出现的编译错误: 

fn main() { 
    let mut s = String::from("hello"); 
 
    change(&mut s); 
} 
 
fn change(some_string: &mut String) { 
    some_string.push_str(", world"); 
}

        首先,我们需要将变量 s 声明为 mut,即可变的。其次,我们使用 &mut s 来给函数传入一个可变引用,并将函数签名修改为 some_string: &mut String 来使其可以接收一个可变引用作为参数。

        但可变引用在使用上有一个很大的限制:对于特定作用域中的特定数据来说,一次只能声明一个可变引用。以下代码尝试违背这一限制,则会导致编译错误:

let mut s = String::from("hello"); 
 
let r1 = &mut s; 
let r2 = &mut s; 

        出现的错误如下所示:

error[E0499]: cannot borrow `s` as mutable more than once at a time
 --> borrow_twice.rs:5:19
  |
4 |     let r1 = &mut s;
  |                   - first mutable borrow occurs here
5 |     let r2 = &mut s;
  |                   ^ second mutable borrow occurs here
6 | }
  | - first borrow ends here

        这个规则使得引用的可变性只能以一种受到严格限制的方式来使用。许多刚刚接触Rust的开发者会反复地与它进行斗争,因为大部分的语言都允许你随意修改变量。

        但另一方面,在Rust中遵循这条限制性规则可以帮助我们在编译时避免数据竞争。数据竞争(data race)与竞态条件十分类似,它会在指令满足以下3种情形时发生:

💫 两个或两个以上的指针同时访问同一空间。

💫 其中至少有一个指针会向空间中写入数据。

💫 没有同步数据访问的机制。 

        数据竞争会导致未定义的行为,由于这些未定义的行为往往难以在运行时进行跟踪,也就使得出现的 bug 更加难以被诊断和修复。

        Rust则完美地避免了这种情形的出现,因为存在数据竞争的代码连编译检查都无法通过!与大部分语言类似,我们可以通过花括号来创建一个新的作用域范围。

        这就使我们可以创建多个可变引用,当然,这些可变引用不会同时存在:

let mut s = String::from("hello");

{
    let r1 = &mut s;
        
} // 由于 r1 在这里离开了作用域,所以我们可以合法地再创建一个可变引用。

let r2 = &mut s;

        在结合使用可变引用与不可变引用时,还有另外一条类似的限制规则,它会导致下面的代码编译失败:

        出现的错误如下所示:

        哇!发现了吗?我们不能在拥有不可变引用的同时创建可变引用。不可变引用的用户可不会希望他们眼皮底下的值突然发生变化!

        不过,同时存在多个不可变引用是合理合法的,对数据的只读操作不会影响到其他读取数据的用户。

        尽管这些编译错误会让人不时地感到沮丧,但是请牢记这一点:Rust编译器可以为我们提早(在编译时而不是运行时)暴露那些潜在的 bug,并且明确指出出现问题的地方。你不再需要去追踪调试为何数据会在运行时发生了非预期的变化。 

3. 悬垂引用

        使用拥有指针概念的语言会非常容易错误地创建出悬垂指针。这类指针指向曾经存在的某处内存地址,但该内存已经被释放掉甚至是被重新分配另作他用了

        而在Rust语言中,编译器会确保引用永远不会进入这种悬垂状态。假如我们当前持有某个数据的引用,那么编译器可以保证这个数据不会在引用被销毁前离开自己的作用域。

        让我们试着来创建一个悬垂引用,并看一看Rust是如何在编译期发现这个错误的: 

fn main() { 
    let reference_to_nothing = dangle(); 
} 
 
fn dangle() -> &String { 
    let s = String::from("hello"); 
 
    &s 
} 

        出现的错误如下所示:

error[E0106]: missing lifetime specifier
 --> dangle.rs:5:16
  |
5 | fn dangle() -> &String {
  |                ^ expected lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but there is
  no value for it to be borrowed from
  = help: consider giving it a 'static lifetime

        这段错误提示信息包含了一个我们还没有接触到的新概念:生命周期,我们会在后文详细讨论。不过,即使我们先将生命周期放置不管,这条错误提示信息也准确地指出了代码中的问题: 

        this function's return type contains a borrowed value, but there is no valuefor it to be borrowed from.[1] (译文:此函数的返回类型包含一个借用的值,但没有可供借用的值。[1])

        回过头来仔细看一看我们的dangle函数中究竟发生了些什么:

fn dangle() -> &String {      // dangle会返回一个指向String的引用
    let s = String::from("hello");  // s被绑定到新的String上 
 
    &s // 我们将指向s的引用返回给调用者 
} // 变量s在这里离开作用域并随之被销毁,它指向的内存自然也不再有效。 
  // 危险! 

        由于变量 s 创建在函数 dangle 内,所以它会在 dangle 执行完毕时随之释放。

        但是,我们的代码依旧尝试返回一个指向 s 的引用,这个引用指向的是一个无效的 String,这可不对!

        Rust成功地拦截了我们的危险代码。解决这个问题的方法也很简单,直接返回 String 就好: 

fn no_dangle() -> String { 
    let s = String::from("hello"); 
 
    s 
} 

        这种写法没有任何问题,所有权被转移出函数,自然也就不会涉及释放操作了。

4. 引用的规则

        让我们简要地概括一下本篇文章对引用的讨论:

💫 在任何一段给定的时间里,你要么只能拥有一个可变引用,要么只能拥有任意数量的不可变引用。

💫 引用总是有效的。

下篇文章中继续讨论另外一种特殊的引用形式:切片。 

 

 

 

 

 

 

 

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/283563.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

气象预报与计算机技术:深度融合与未来展望

气象预报与计算机技术:深度融合与未来展望 一、引言 气象预报,作为人类对自然界气象变化的探索与预测,随着时间的推移和技术的进步,已经逐渐从单纯的经验模式转变为依赖于精密的仪器与强大的计算机技术的科学预测。在本文中,我们将深入探讨气象预报与计算机技术之间的密…

[2024区块链开发入门指引] - 比特币与区块链诞生

一份为小白用户准备的免费区块链基础教程 工欲善其事,必先利其器 Web3开发中,各种工具、教程、社区、语言框架.。。。 种类繁多,是否有一个包罗万象的工具专注与Web3开发和相关资讯能毕其功于一役? 参见另一篇博文👉 2024最全面…

数据结构:堆的三部曲 (一)堆的实现

堆的实现 1.堆的结构1.1堆的定义理解 2.堆的实现(以小根堆为例)2.1 堆结构体的定义2.2 堆的插入交换函数向上调整算法插入函数的代码 2.3 堆的删除向下调整算法:删除函数的代码: 2.4其他操作 3.测试以及完整源代码实现3.1测试代码…

山西电力市场日前价格预测【2024-01-02】

日前价格预测 预测说明: 如上图所示,预测明日(2024-01-02)山西电力市场全天平均日前电价为92.93元/MWh。其中,最高日前电价为275.90元/MWh,预计出现在18:00。最低日前电价为0.00元/MWh,预计出现…

【数据结构与算法】第1章绪论(头歌习题)【合集】

文章目录 第1关:求和任务描述编程要求代码 第2关:求倒数和的倒数任务描述编程要求完整代码 第3关:回文数任务描述编程要求完整代码 第4关:求素数个数任务描述编程要求完整代码 第5关:最大因子任务描述编程要求完整代码…

第6课 用window API捕获麦克风数据并加入队列备用

今天是2024年1月1日,新年的第一缕阳光已经普照大地,祝愿看到这篇文章的所有程序员或程序爱好者都能在新的一年里持之以恒,事业有成。 今天也是我加入CSDN的第4100天,但回过头看一看,这么长的时间也没有在CSDN写下几篇…

2023-刻苦自励,2024-奋起直追!

点击上方“嵌入式应用研究院”,选择“置顶/星标公众号” 干货福利,第一时间送达! 来源 | 嵌入式应用研究院 整理&排版 | 嵌入式应用研究院 一、引言 时光如影,岁月如梭。转眼之间,2023年已经过去,在这一…

拒绝采样(算法)总结

先说说什么是拒绝采样算法:就类似于数学上的求阴影面积的方法,直接求求不出来,就用大面积 - 小面积 阴影面积的办法。 所谓拒绝 和 采样 :就像是撒豆子计个数,计算概率问题一样,大桶里面套小桶&#xff0c…

[C#]OpenCvSharp结合yolov8-face实现L2CS-Net眼睛注视方向估计或者人脸朝向估计

源码地址: github地址:https://github.com/Ahmednull/L2CS-Net L2CS-Net介绍: 眼睛注视(eye gaze) 是在各种应用中使用的基本线索之一。 它表示用户在人机交互和开放对话系统中的参与程度。此外,它还被用…

Docker 从入门到实践:Docker介绍

前言 在当今的软件开发和部署领域,Docker已经成为了一个不可或缺的工具。Docker以其轻量级、可移植性和标准化等特点,使得应用程序的部署和管理变得前所未有的简单。无论您是一名开发者、系统管理员,还是IT架构师,理解并掌握Dock…

【数据结构】栈和队列(队列的基本操作和基础知识)

🌈个人主页:秦jh__https://blog.csdn.net/qinjh_?spm1010.2135.3001.5343🔥 系列专栏:《数据结构》https://blog.csdn.net/qinjh_/category_12536791.html?spm1001.2014.3001.5482 ​ 目录 前言 队列 队列的概念和结构 队列的…

域名转移:将腾讯云转移至阿里云

当时注册域名时,腾讯域云相对便宜,但目前阿里云在业界更加成熟,因此将自己申请的域名由阿里云转移至阿里云,并记录转移过程。 一、域名转出 进入腾讯云,登陆后选择控制台,选择我的资源–域名注册–全部域名…

【华为机试】2023年真题B卷(python)-滑动窗口最大值

一、题目 题目描述: 有一个N个整数的数组,和一个长度为M的窗口,窗口从数组内的第一个数开始滑动直到窗口不能滑动为止, 每次窗口滑动产生一个窗口和(窗口内所有数的和),求窗口滑动产生的所有窗口…

LTPI协议的理解——1、LTPI协议的定义和结构

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 LTPI协议的理解——1、LTPI协议的定义和结构 定义DC-SCM 2.0 LTPI 结构GPIO通道I2C/SMBus通道Uart通道OEM通道数据通道 总结 定义 LTPI (LVDS Tunneling Protocol & Int…

单片机数据发送程序

#include<reg51.h> //包含单片机寄存器的头文件 /***************************************************** 函数功能&#xff1a;向PC发送一个字节数据 ***************************************************/ void Send(unsigned char dat) { SBUFdat; whil…

【ESP-NOW with ESP32:从多个开发板接收数据(多对一)】

【ESP-NOW with ESP32&#xff1a;从多个开发板接收数据&#xff08;多对一&#xff09;】 1. 项目概况2. 先决条件2.1 环境配置2.2 所需零件 3. 获取接收板 MAC 地址4. ESP32 发送码 &#xff08;ESP-NOW&#xff09;4.1 代码的工作原理4.2 setup&#xff08;&#xff09;4.3 …

异步处理方案

目录 1.通过promise的链式调用将异步方法变为同步执行 2.使用async及await 3.回调函数方式 4.三种方式对比 5.async及await使用的注意点 1.通过promise的链式调用将异步方法变为同步执行 function get1(){return new Promise((resolve,reject) >{console.log(执行get1接…

B端产品学习-市场调研与分析

B端产品市场调研与分析 目录&#xff1a; 为什么要做产品调研 B端产品调研对比C端产品调研 B端产品调研要怎么做 为什么要做产品调研 杰克特劳特说过&#xff1a;“成为唯一。如果不能争得第一&#xff0c;那就找到一个能够成为第一的细分&#xff0c;这就是定位的第一法则…

激发大规模ClickHouse数据加载(3/3)确保加载大规模数据的可靠性

本文字数&#xff1a;7016&#xff1b;估计阅读时间&#xff1a;18 分钟 作者&#xff1a;Tom Schreiber 审校&#xff1a;庄晓东&#xff08;魏庄&#xff09; 本文在公众号【ClickHouseInc】首发 本文是“激发大规模ClickHouse数据加载”系列文章的最后一篇&#xff1a; 激发…

【华为机试】2023年真题B卷(python)-猴子爬山

一、题目 题目描述&#xff1a; 一天一只顽猴想去从山脚爬到山顶&#xff0c;途中经过一个有个N个台阶的阶梯&#xff0c;但是这猴子有一个习惯&#xff1a; 每一次只能跳1步或跳3步&#xff0c;试问猴子通过这个阶梯有多少种不同的跳跃方式&#xff1f; 二、输入输出 输入描述…