rust编程-rust所有权理解(chapter 4.2 引用实质是借用)

news2024/11/24 6:19:54

目录

2. 引用与借用

2.1 可变(mutable)引用

2.2 悬空(dangling)引用

2.3 引用的规则总结


2. 引用与借用

上一章节中提到,所有权在函数调用中的转移,函数返回必须同时返还所有权,才能使函数调用后能继续使用某个变量(如上一章节中的String变量)。

Rust支持引用类型,引用类型不需要转移(move)所有权。 引用类似一个指针,能利用该指针指向的地址进行访问其它变量所有权的数据。但与指针不同,引用保证指向一个特定类型的有效值。 如下:

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是一个String的引用类型
    s.len()
} // 至此,s离开其作用域,但s不具有其值的所有权,因此Rust不会自动调用drop进行销毁

可以看到,函数形参使用了&String取代String类型,函数实参使用&s1取代了s1。 这种&号就是表示引用,允许引用到某个值,但并不拥有该值的所有权。如下图原理:

引用(&)的反向操作是解引用,使用*操作符完成。在第8章节中将介绍解应用操作符的使用,并在第15章节详细介绍解应用。

具体地展开细看,如下:

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

    let len = calculate_length(&s1);

&s1语法创建了一个指向但不拥有s1的引用,因此该应用在不使用后,不会调用drop。同样地,函数声明的原型也使用&来标识其形参s是String的引用类型。使用引用类型的函数,就不需要在函数返回时,返回该形参变量以归还所有权,因为引用本身就没有拿到所有权。

我们称引用这种行为为borrowing(借用),因为不具有所有权,不能通过引用来修改其值。

如下:

fn main() {
    let s = String::from("hello");

    change(&s);
}

fn change(some_string: &String) {
    some_string.push_str(", world");
}

编译时会报错:

$ cargo run
   Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&` reference
 --> src/main.rs:8:5
  |
7 | fn change(some_string: &String) {
  |                        ------- help: consider changing this to be a mutable reference: `&mut String`
8 |     some_string.push_str(", world");
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `some_string` is a `&` reference, so the data it refers to cannot be borrowed as mutable

For more information about this error, try `rustc --explain E0596`.
error: could not compile `ownership` due to previous error

如同变量默认是不可变的,引用也是不可改变的。不允许通过引用来更改其指向的值。

2.1 可变(mutable)引用

通过mut关键词可以运行通过引用来修改一个“借用”的值。如下:

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

    change(&mut s);
}

fn change(some_string: &mut String) {
    some_string.push_str(", world");
}

1)函数原型定义成&mut的可变引用String类型

2)函数调用传递可变的引用实参&mut s

3) 原始字符串类型定义成mut的可变字符串类型

可变引用有一个很大的限制:特定时间,只能有一个对特定数据的可变引用。 如下试图创建两个对s的可变引用将失败:

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

    let r1 = &mut s;
    let r2 = &mut s;

    println!("{}, {}", r1, r2);

编译时将报错:

# cargo run
   Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0499]: cannot borrow `s` as mutable more than once at a time
 --> src/main.rs:5:14
  |
4 |     let r1 = &mut s;
  |              ------ first mutable borrow occurs here
5 |     let r2 = &mut s;
  |              ^^^^^^ second mutable borrow occurs here
6 | 
7 |     println!("{}, {}", r1, r2);
  |                        -- first borrow later used here

For more information about this error, try `rustc --explain E0499`.
error: could not compile `ownership` due to previous error

不能在同一时间多次将s借为可变的,第一个可变的borrow在r1中,必须持续到println中使用它。但是在这个可变引用的创建和它的使用之间,在r2中创建另一个可变引用,它借用了与r1相同的数据。

禁止同时对同一数据进行多个可变引用的限制可以来控制值的可改变性。这对于Rust新手来说,是比较难以掌握的,因为大多数语言允许变量随时随地被改动。这个限制的好处是:Rust可以在编译时即消除数据竞争,这是Rust的一大特色。 数据竞争类似于竞争条件(Race Condition),当出现以下三种行为时发生:

  • 两个或多个指针相同时刻访问相同的数据

  • 至少使用其中一个指针执行数据写入操作

  • 没有任何机制用来来同步对数据的访问

数据竞争会导致未定义的行为,在运行时追踪时,非常难以诊断和修复数据竞争带来的错误。Rust通在编译时,报错拒绝数据竞争的代码来避免数据竞争问题的出现。

可以利用作用域来控制可变引用的生命周期,避免同时多个可变引用,如下:

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

    {
        let r1 = &mut s;
    } // r1 离开作用域,销毁。

    let r2 = &mut s; // 可以定义新的可变引用到s

与可变引用不同的是,Rust允许同时多个指向相同数据的不可变引用。 如下:

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

    let r1 = &s; // 合法
    let r2 = &s; // 合法
    let r3 = &mut s; // 非法

    println!("{}, {}, and {}", r1, r2, r3);

编译错误如下:

# cargo run
   Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
 --> src/main.rs:6:14
  |
4 |     let r1 = &s; // no problem
  |              -- immutable borrow occurs here
5 |     let r2 = &s; // no problem
6 |     let r3 = &mut s; // BIG PROBLEM
  |              ^^^^^^ mutable borrow occurs here
7 | 
8 |     println!("{}, {}, and {}", r1, r2, r3);
  |                                -- immutable borrow later used here

For more information about this error, try `rustc --explain E0502`.
error: could not compile `ownership` due to previous error

当存在指向相同值的不可变引用时,也不能有可变引用。

注意,引用的作用域从它被引入的地方开始,一直延续到该引用最后一次被使用。 例如,如下代码是可以编译成功的,因为不可变引用的最后一次使用println!,在引入可变引用之前发生:

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

    let r1 = &s; // 合法
    let r2 = &s; // 合法
    println!("{} and {}", r1, r2);
    // r1和r2之后没有被使用到

    let r3 = &mut s; // r1,r2后面无使用,这里可以编译成功
    println!("{}", r3);

不可变引用r1和r2的作用域只到在println!最后使用它们的地方,也就是在可变引用r3创建之前。

这些作用域不重叠,因此s3可变引用的定义代码是合法的。编译器能判断出某个引用在作用域结束之前就不再被使用的能力被称为非词法生命周期(Non-Lexical lifetime,简称NLL),可以参考如下链接:

https://blog.rust-lang.org/2018/12/06/Rust-1.31-and-rust-2018.html#non-lexical-lifetimes

尽管使用借用导致的错误可能令编程人员沮丧,但Rust编译器在早期(在编译时而非运行时)发现并指出了潜在的错误,准确地指出了问题所在,避免了在运行时追踪非期望结果的复杂。

2.2 悬空(dangling)引用

在有指针的编程语言中,当通过指针释放内存,同时保留指向该内存的指针时,错误地保留一个非法的指针称作悬空指针,其引用内存可能已经分配他用。

在Rust中,编译器保证了引用永远不会是悬空引用。如果对某些数据有引用,编译器将确保数据不会在对数据的引用之前离开作用域(所有权不会被转移他用)。 如下示例:

fn main() {
    let reference_to_nothing = dangle(); // reference_to_nothing是借用,
                    // 没有所有权
}

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

    &s // 返回一个到字符串s的引用,主调函数产生一次borrowing
} // s离开作用域,被drop,内存被释放回收

当dangle()函数返回时,字符串s实际已经被销毁。返回的借用的引用变成“悬挂”引用,编译器报错如下:

# cargo run
   Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0106]: missing lifetime specifier
 --> src/main.rs:5:16
  |
5 | fn dangle() -> &String {
  |                ^ expected named 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 using the `'static` lifetime
  |
5 | fn dangle() -> &'static String {
  |                ~~~~~~~~

For more information about this error, try `rustc --explain E0106`.
error: could not compile `ownership` due to previous error

2.3 引用的规则总结

  • 任何时刻,可以有一个可变的引用,或者多个不可变的引用

  • 引用必须是有效的(不能是悬挂的)

关于作者:

犇叔,浙江大学计算机科学与技术专业,研究生毕业,而立有余。先后在华为、阿里巴巴和字节跳动,从事技术研发工作,资深研发专家。主要研究领域包括虚拟化、分布式技术和存储系统(包括CPU与计算、GPU异构计算、分布式块存储、分布式数据库等领域)、高性能RDMA网络协议和数据中心应用、Linux内核等方向。

专业方向爱好:数学、科学技术应用

关注犇叔,期望为您带来更多科研领域的知识和产业应用。

内容坚持原创,坚持干货有料。坚持长期创作,关注犇叔不迷路

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

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

相关文章

[附源码]计算机毕业设计勤工助学管理系统Springboot程序

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: SSM mybatis Maven Vue 等等组成,B/S模式 M…

jquery 登录-记住密码

jquery 登录-记住密码在登录时,添加记住密码功能:用的 localStorage 存储和获取登录信息 //存储 var username $("input[nameusername]").val(); var password $("input[namepassword]").val(); var validateCode $("inpu…

自动驾驶:2022 apollo day 观后感(一)

2022 apollo day 观后感(一)注: ppt来自apollo day,结合ppt,讲述一些自己的想法,欢迎批评指正!Topic One -- 打造安全、智能、高效的自动驾驶技术体系(陈竞凯)无人驾驶技…

Ruby ERB模板注入检测

了解Ruby ERB模板注入,Ruby ERB模板注入检测。 ERB是Ruby自带的 <% 写逻辑脚本(Ruby语法) %><%= 直接输出变量值或运算结果 %>require erbtemplate = "text to be generated: <%= x %>" erb_object = ERB.new(template) x = 5 puts erb_object.r…

[附源码]JAVA毕业设计人才公寓管理系统(系统+LW)

[附源码]JAVA毕业设计人才公寓管理系统&#xff08;系统LW&#xff09; 项目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术…

Linux——Xshell、Xftp实现Linux远程登录与应用

目录 一、远程登录 1.1 SSH登录方式 二、Xshell远程连接 2.1 远程连接 2.2 设置粘贴复制 三、Xftp远程连接 3.1 远程连接 3.2 解决乱码 3.3 传输文件 一、远程登录 通常在工作过程中&#xff0c;公司中使用的真实服务器或者是云服务器&#xff0c;都不允许除运维人员 之…

微电网优化调度|农村农业区可再生能源微电网优化调度(Python代码实现)

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f389;作者研究&#xff1a;&#x1f3c5;&#x1f3c5;&#x1f3c5;主要研究方向是电力系统和智能算法、机器学…

软件测试就业现状分析,2023是卷还是润?

一、当前软件测试的现状是什么&#xff1f; 现状1、网络上大量人唱衰&#xff0c;测试就业不行了 2022年5月后&#xff0c;越来越多人网络发声“互联网行业不行了”。贴吧、知乎、小红书上&#xff0c;也有大量人反馈软件测试就业惨淡……&#xff0c;篇幅有限&#xff0c;仅…

Docker学习4-常用命令之重要的容器命令

本文是Docker学习系列教程中的第四篇。本文是Docker常用命令中的重要命令。为什么说重要呢&#xff1f;因为这些命令&#xff0c;在以后开发过程中&#xff0c;会经常使用到。比如&#xff1a;怎么查看容器中运行的日志&#xff1f;怎么查看容器运行的进程&#xff1f;怎么导出…

最新中文版本FLStudio21水果音乐软件更新下载

导读&#xff1a;昨晚Image-Line发布FL Studio 2023&#xff0c;而今年也是他们成立第23周年。FL21一经发行便引起了广大制作人的关注&#xff0c;今天我们来介绍一下这款软件。FL Studio是一款音乐编曲软件&#xff0c;全称&#xff1a;Fruity Loops Studio&#xff0c;也是我…

实战:Kind部署k8s集群-2022.12.6(成功测试)

写在前面 原文阅读效果更佳&#xff1a;实战&#xff1a;Kind部署k8s集群-2022.12.6(成功测试) 语雀 《实战&#xff1a;Kind部署k8s集群-2022.12.6(成功测试)》 Kind Kind 是 Kubernetes in Docker 的简写&#xff0c;是一个使用 Docker 容器作为 Node 节点&#xff0c;在…

通关算法题之 ⌈栈和队列⌋

栈和队列 155. 最小栈 设计一个支持 push &#xff0c;pop &#xff0c;top 操作&#xff0c;并能在常数时间内检索到最小元素的栈。 实现 MinStack 类: MinStack() 初始化堆栈对象。void push(int val) 将元素val推入堆栈。void pop() 删除堆栈顶部的元素。int top() 获取…

文件系统实现

文件系统实现&#x1f3de;️1. 整体组织&#x1f301;2. 文件组织&#xff1a;inode&#x1f320;3. 多级索引&#x1f4d6;3.1 间接指针&#x1f4d6;3.2 多重间接指针&#x1f4d6;3.3 基于范围的方法&#x1f30c;4. 目录组织⛺5. 空闲空间管理&#x1f33f;6. 读取和写入文…

Vue 官方文档2.x教程学习笔记 1 基础 1.7 条件渲染

Vue 官方文档2.x教程学习笔记 文章目录Vue 官方文档2.x教程学习笔记1 基础1.7 条件渲染1.7.1 v-if1.7.2 在\<template> 元素上使用 v-if条件渲染分组1.7.3 v-else1.7.4 v-else-if1.7.5 用 key 管理可复用的元素1.7.6 v-show1.7.7 v-if vs v-show1.7.8 v-if 与 v-for 一起…

小网SIM卡QMI拨号无法获取IPv6地址问题的分析

背景 客户反馈设备插小网卡驻网并加载qmi_wwan驱动后,使用多路拨号工具进行两路拨号,第一路无法获取IPv6地址,但是插现网卡测试是没有问题的。具体测试方法如下图: Check后只有第二路有PDN驻网请求,如下图,建议排查QMI拨号工具 分析流程 首先根据客户提供的方法对问…

day6_redis学习

文章目录关注和取关查看其他用户界面及共同关注关注推送关注和取关 因为关注用户的时候可能涉及到共同关注的对象&#xff0c;所以需要利用到交集&#xff0c;而在Redis中可以使用交集的&#xff0c;是Set以及ZSet数据结构&#xff0c;但是显然这里并不需要排序&#xff0c;所…

Java学习之多态二

目录 一、运用多态解决宠物喂食问题 原理分析 运行测试 运行结果 分析 增加宠物和食物种类 Pig类 Rice类 测试 运行结果 一、运用多态解决宠物喂食问题 改变Master类的feed方法的参数列表 package com.hspedu.poly_;public class Master {private String name;public…

基于机器学习之模型树短期负荷预测(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f4dd;目前更新&#xff1a;&#x1f31f;&#x1f31f;&#x1f31f;电力系统相关知识&#xff0c;期刊论文&…

Python 中的 Raincloud 图绘制

Python 中的 Raincloud 图 提示&#xff1a;一种强大的数据可视化方法&#xff0c;由小提琴图、散点图和箱线图的组合组成 提示&#xff1a;目录 Python 中的 Raincloud 图绘制Python 中的 Raincloud 图前言一、什么是 Raincloud 图&#xff1f;二、使用步骤1.加载数据集2.读入…

S7协议抓包分析(附pcap数据包)

一、S7协议概述 1、S7协议简介 S7comm&#xff08;S7 通信&#xff09;是西门子专有协议&#xff0c;可在西门子 S7-300/400 系列的可编程逻辑控制器 (PLC) 之间运行。它用于 PLC 编程、PLC 之间的数据交换、从 SCADA&#xff08;监控和数据采集&#xff09;系统访问 PLC 数据…