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 isno 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.mzph.cn/news/591425.shtml

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

相关文章

Java学习,一文掌握Java之SpringBoot框架学习文集(1)

🏆作者简介,普修罗双战士,一直追求不断学习和成长,在技术的道路上持续探索和实践。 🏆多年互联网行业从业经验,历任核心研发工程师,项目技术负责人。 🎉欢迎 👍点赞✍评论…

我有实体店,为什么要做小程序

做小程序对于实体店来说有以下几个好处: 拓展线上渠道:随着移动互联网的普及,越来越多的消费者习惯在手机上进行购物和搜索相关信息。通过做小程序,你可以将线下实体店与线上渠道相结合,提供在线购买、预约、查询等功能…

Windows搭建Emby媒体库服务器,无公网IP远程访问本地影音文件

文章目录 1.前言2. Emby网站搭建2.1. Emby下载和安装2.2 Emby网页测试 3. 本地网页发布3.1 注册并安装cpolar内网穿透3.2 Cpolar云端设置3.3 Cpolar内网穿透本地设置 4.公网访问测试5.结语 1.前言 在现代五花八门的网络应用场景中,观看视频绝对是主力应用场景之一&…

pycharm中Pyside2/QtDesigner安装和配置

目录 1、安装pyqt5 2、安装pyqt5-tools 3、在pycharm中配置Qt Designer PyQt5/QtDesigner安装和配置 1、安装pyqt5 pip install pyqt5 安装了 pyqt5 之后,在 python 安装目录下面的 Scripts 文件夹中,有一个 pyuic5.exe 文件,这个可执行文…

大数据概念:数据网格和DataOps

数据网格(Data Mesh) 一种新型的数据架构模式,旨在解决传统数据架构中存在的一些问题,例如数据孤岛、数据冗余、数据安全等。数据网格将数据作为一种服务,通过在分布式环境中提供数据服务,实现数据的共享和…

c++ 静态联编+动态联编 (多态)

静态多态 动态多态 1)静态多态和动态多态的区别就是函数地址是早绑定(静态联编)还是晚绑定(动态联编)。 如果函数的调用,在编译阶段就可以确定函数的调用地址,并产生代码,就是静态多态(编译时多态),就是说地址是早绑定…

Flink实时电商数仓(十)

common模块回顾 app BaseApp: 作为其他子模块中使用Flink - StreamAPI的父类,实现了StreamAPI中的通用逻辑,在其他子模块中只需编写关于数据处理的核心逻辑。BaseSQLApp: 作为其他子模块中使用Flink- SQLAPI的父类。在里面设置了使用SQL API的环境、并行…

数据库攻防学习之Redis

Redis 0x01 redis学习 在渗透测试面试或者网络安全面试中可能会常问redis未授权等一些知识,那么什么是redis?redis就是个数据库,常见端口为6379,常见漏洞为未授权访问。 0x02 环境搭建 这里可以自己搭建一个redis环境&#xf…

文件监控软件丨文件权限管理工具

文件已经成为企业最重要的资产之一。然而,文件的安全性和完整性经常受到威胁,如恶意软件感染、人为误操作、内部泄密等。 为了确保文件的安全,文件监控软件应运而生。本文将深入探讨文件监控软件的概念、功能、应用场景和未来发展等方面。 文…

7、InternVL

简介 github demo 使用网络获取的油画图片,InternVL识别还算可以。 使用stable diffusion生成的图片,InternVL能很好的识别。 权重 huggingface地址 模型搭建 github地址 下载源码 git clone https://github.com/OpenGVLab/InternVL.git创建环…

Windows 使用 nmap软件测试 UDP 端口

下载windows版nmap ,下载后双机默认安装。 Download the Free Nmap Security Scanner for Linux/Mac/Windows 打开CMD , 输入 cd C:\Program Files (x86)\Nmap C:\Program Files (x86)\Nmap>ncat -z -v -u ntp.aliyun.com 123 Ncat: Version 7.80 ( …

【HarmonyOS开发】共享包HAR和HSP的创建和使用以及三方库的发布

OpenHarmony提供了两种共享包,HAR(Harmony Archive)静态共享包,和HSP(Harmony Shared Package)动态共享包。 HAR与HSP都是为了实现代码和资源的共享,都可以包含代码、C库、资源和配置文件&…

redis的搭建及应用(七)-redis的限流插件redis-cell

Redis限流插件-redis-cell redis-cell 是一个用rust语言编写的基于令牌桶算法的的限流模块,提供原子性的限流功能,并允许突发流量,可以很方便的应用于分布式环境中。 下载redis-cell插件 访问Releases brandur/redis-cell (github.com) 上传…

计算机网络——应用层与网络安全(六)

前言: 前几章我们已经对TCP/IP协议的下四层已经有了一个简单的认识与了解,下面让我们对它的最顶层,应用层进行一个简单的学习与认识,由于计算机网络多样的连接形式、不均匀的终端分布,以及网络的开放性和互联性等特征&…

Python流星雨完整代码

文章目录 环境需求完整代码详细分析环境需求 python3.11.4PyCharm Community Edition 2023.2.5pyinstaller6.2.0(可选,这个库用于打包,使程序没有python环境也可以运行,如果想发给好朋友的话需要这个库哦~)【注】 python环境搭建请见:https://want595.blog.csdn.net/arti…

找第三方数据公司获取电商平台商品数据订单数据店铺信息等

API文档 如何获取? 应用业务场景(不限)

京东tp3手势验证

2024祝我们越来越好。 新年第二天,来看下这最新的tp3手势验证码,很在之前就发过一篇,最近看了看更新了一个东西,但是难点还是在轨迹上面,感兴趣的朋友可以去看看。 risk_jd[jstub] 改了下这,之前我都没带…

基于ThinkPHP的云盘系统Cloudreve本地搭建并实现远程访问

文章目录 1、前言2、本地网站搭建2.1 环境使用2.2 支持组件选择2.3 网页安装2.4 测试和使用2.5 问题解决 3、本地网页发布3.1 cpolar云端设置3.2 cpolar本地设置 4、公网访问测试5、结语 1、前言 自云存储概念兴起已经有段时间了,各互联网大厂也纷纷加入战局&#…

Flume基础知识(一):Flume组成原理与架构

1. Flume定义 Flume是Cloudera提供的一个高可用的,高可靠的,分布式的海量日志采集、聚合和传输的系统。Flume基于流式架构,灵活简单。 Flume最主要的作用就是,实时读取服务器本地磁盘的数据,将数据写入到HDFS。 2. Fl…

关于标准那些事——第六篇 四象之“白虎”(要素的编写)

两仪生四象——东方青龙(木)、西方白虎(金)、南方朱雀(火)、北方玄武(水) 分别对应标准编写之四象——层次的编写、要素的编写、要素的表述、格式的编排。 今天来分享一下 要素的编…