009、引用

news2024/11/25 10:30:46

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. 引用的规则

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

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

💫 引用总是有效的。

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

 

 

 

 

 

 

 

 

 

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

病情聊天机器人,利用Neo4j图数据库和Elasticsearch全文搜索引擎相结合

项目设计目的: 本项目旨在开发一个病情聊天机器人,利用Neo4j图数据库和Elasticsearch全文搜索引擎相结合,实现对病情相关数据的存储、查询和自动回答。通过与用户的交互,机器人可以根据用户提供的症状描述,给出初步的可…

【C++杂货铺】C++11新特性——可变参数模板

文章目录 一、可变模板参数相关概念的引入二、获取参数包中参数的个数三、递归函数方式展开参数包四、逗号表达式展开参数包五、可变模板参数的实际应用——emplace相关接口5.1 回顾一下 push_back 的三种用法5.2 emplace_back 使用方法介绍5.3 听说 emplace_back 可以提高效率…

SCT2601,可替代LMR16006X/MP2459/MP2456;4.5V-60V Vin,0.6A,高效降压DCDC转换器

•宽输入范围:4.5V-60V •高达0.6A的连续输出电流 •0.765V2.5%反馈参考电压 •集成500mΩ高压侧MOSFET •低静态电流为80uA •轻负载下的脉冲跳过模式(PSM) •最小接通时间80ns •内置6ms软启动时间 •开关频率为700KHz •可编程输入电压欠压…

探秘AI数字人克隆系统OEM源码:实现24小时无人值守直播间的奥秘

随着人工智能技术的不断发展,AI数字人克隆系统OEM源码正在引起广泛的关注。其中,实现24小时无人值守直播间成为了许多企业和机构的追求。本文将深入探讨如何利用AI数字人克隆系统OEM源码实现24小时无人值守直播间,并揭示其背后的奥秘。 一、…

Acrel-2000MG工商业储能站能量管理系统-光伏储能一体化解决方案

安科瑞 崔丽洁 Acrel-2000MG储能能量管理系统是安科瑞专门针对工商业储能电站研制的本地化能量管理系统,可实现了储能电站的数据采集、数据处理、数据存储、数据查询与分析、可视化监控、报警管理、统计报表、策略管理、历史曲线等功能。其中策略管理,支…

【活动回顾】ABeam 德硕 | 企业座谈及宣讲会--石河子大学专场

ABeam 石河子大学校招之旅 沙漠绿洲,戈壁明珠 近日,ABeam大中华区董事长兼总经理中野洋辅先生带领西安招聘团队来到新疆维吾尔自治区石河子市——石河子大学,与信息科学与技术学院领导就校企合作事宜进行了深入的座谈交流,并在随…

【Unity入门】UGUI之Slider(滑动条)

目录 一、什么是Slider?二、Slider属性与功能 一、什么是Slider? Slider控件允许用户可以通过鼠标来在预先确定的范围调节数值 我们可以在Hierarchy视图右键 -> UI ->Slider来创建滑动条 通过上图可以发现Unity内置的Slider主要有3部分&#x…

【web】vue 播放后端(flask)发送的 mp3 文件

文章目录 演示后端(flask)前端(vue3)重要说明 演示 后端(flask) 后端返回的是 mp3 文件的 url,是可以直接在浏览器上打开后播放的处理跨域请求pip install flask-cors后端代码from flask impor…

一场高规格品鉴会,窥探剑南春冲击高端的“野心”

执笔 | 洪大大 编辑 | 扬 灵 12月27日,以“锦绣巴蜀品味东方”为主题的剑南春东方红致敬中国时代品鉴会在有云鹿洄天府1911中国川菜体验中心举办,数十位媒体代表和嘉宾汇聚一堂,共同品味剑南春东方红的品质魅力,感知高端白酒品…

什么是缓存、为什么要用缓存、缓存分类、缓存测试、缓存更新、缓存设计考虑点、缓存测试点

一、缓存 缓存是一种将数据存储在高速缓存中的技术,它可以提高应用程序的性能和响应速度。 二、 为什么要用缓存 1. 高性能(主要目的) 查询耗时,但变化少,又有很多读请求情况下,可以将查询结果放到缓存中。减少对数据库的压力&…

光伏、储能一体化监控及运维解决方案 安科瑞 许敏

前言:今年以来,在政策利好推动下光伏、风力发电、电化学储能及抽水蓄能等新能源行业发展迅速,装机容量均大幅度增长,新能源发电已经成为新型电力系统重要的组成部分,同时这也导致新型电力系统比传统的电力系统更为复杂…

clang-format

Clang-Format Clang-Format Style Options — Clang 18.0.0git documentation VSCode 1.1 安装扩展 C 1.2 设置 1.3 使用 .clang-fornat 放置在项目(代码)文件夹下使用 .clang-fornat 为文件名 --- # https://clang.llvm.org/docs/ClangFormatStyle…

document360的替代品:原来它也这么好用

在当今信息爆炸的时代,企业和组织需要一个高效的知识管理系统来整理、存储和共享知识。document360是一款备受欢迎的知识库管理系统,但是它并非唯一的选择。本文将介绍document360的替代品,感兴趣就往下看吧。 首先,让我们来了解…

Python分析了京东7万+条评论,仅用3分钟,结果发现……

之前学习Python爬虫采集,为了练手用Scrapy写了一个爬虫,整整采集了京东平台vivo旗舰店7万多条评论。一直也没觉得这些评论数据有啥用,就留在MongoDB中吃灰。最近学了jieba和wordcloud之后,突发奇想着分析下这7万多条评论数据&…

【网络安全】上网行为代理服务器启用Alerts

文章目录 启用AlertsAlert Limits per 24 hoursEmail AlertsSystem Alerts Suspicious Activity AlertsPermitted Suspicious Activity AlertsBlocked Suspicious Activity Alerts Protocol&Category Usage Alerts告警邮件范例推荐阅读 这里的Web Proxy主要代指proxy serve…

HarmonyOS应用开发-搭建开发环境

本文介绍如何搭建 HarmonyOS 应用的开发环境,介绍下载安装 DevEco Studio 开发工具和 SDK 的详细流程。华为鸿蒙 DevEco Studio 是面向全场景的一站式集成开发环境,面向全场景多设备,提供一站式的分布式应用开发平台,支持分布式多…

声纹识别资源汇总(不断更新)

目录 一、任务说明二、指标三、声纹识别研究现状四、数据集开源(1)VoxCeleb:(2)WSJ and LibriSpeech Corpus(3)VOiCES Dataset(4)English Multi-speaker Corpus for Voic…

机器学习笔记(四)初识卷积神经网络

前言 第一次写卷积神经网络,也是照着paddlepaddle的官方文档抄,这里简单讲解一下心得。 首先我们要知道之前写的那些东西都是什么,之前写的我们称之为简单神经网络,也就是简单一层连接输出和输出,通过前向计算和逆向…

LM358 典型应用Multisim仿真设计

一、LM358简介: LM358 运算放大器属于一种低功率双运算放大器,由两个独立的高增益内部频率补偿运算放大器组成,专门设计用于在宽电压范围内由单电源供电。LM358 运算放大器具有低功耗、共模输入电压范围扩展到地/VEE以及单电源或双电源操作。…

代表团坐车 - 华为OD统一考试

OD统一考试(B卷) 分值: 100分 题解: Java / Python / C 题目描述 某组织举行会议,来了多个代表团同时到达,接待处只有一辆汽车可以同时接待多个代表团,为了提高车辆利用率,请帮接待…