Rust - 可变引用和悬垂引用

news/2024/11/30 0:40:12/

可变引用

在上一篇文章中,我们提到了借用的概念,将获取引用作为函数参数称为 借用borrowing),通常情况下,我们无法修改借来的变量,但是可以通过可变引用实现修改借来的变量。代码示例如下:

fn main() {let mut s = String::from("hello");  // s是可变的变量change(&mut s);  // &mut 表示可变引用
}fn change(some_string: &mut String) {  // &mut 表示可变引用some_string.push_str(", world");
}

要想实现修改借来的变量就必须将 s改为 mut。然后必须创建一个可变引用 &mut s和接受一个可变引用 some_string: &mut String

但是可变引用有一个很大的限制:在特定作用域中的特定数据只能有一个可变引用。比如下述代码就不会被成功编译。

fn main() {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--> 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

所以这种修改借来的变量的可变引用是以一种受限制的方式允许修改,这个限制的好处是 Rust 可以在编译时就避免数据竞争。数据竞争data race)类似于竞态条件,它可由这三个行为造成:

  • 两个或更多指针同时访问同一数据。
  • 至少有一个指针被用来写入数据。
  • 没有同步数据访问的机制。

数据竞争会导致未定义行为,难以在运行时追踪,并且难以诊断和修复;Rust 避免了这种情况的发生。我们可以使用{}创建一个新的作用域,这样就能够允许多个可变引用了,只是不能在同一个作用域中同时拥有:

fn main() {let mut s = String::from("hello");{let r1 = &mut s;} // r1 在这里离开了作用域,所以我们完全可以创建一个新的引用let r2 = &mut s;
}

另外还需要注意的是,不能在拥有不可变引用的同时拥有可变引用。不可变引用的用户可不希望在他们的眼皮底下值就被意外的改变了!但是多个不可变引用是可以的,因为没有哪个只能读取数据的人有能力影响其他人读取到的数据。如下述代码:

fn main() {let mut s = String::from("hello");let r1 = &s; // 没问题let r2 = &s; // 没问题let r3 = &mut s; // 在拥有不可变引用的同时拥有可变引用println!("{}, {}, and {}", r1, r2, r3);} 

上面代码示例编译时会抛出如下异常:

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

但是如果可变引用和不可变引用他们的作用域不重叠代码就是可以编译的,我们可以将上面的代码示例进行修改就可以正常运行了。

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);}

悬垂引用(Dangling References

在具有指针的语言中,很容易通过释放内存时保留指向它的指针而错误地生成一个 悬垂指针dangling pointer),所谓悬垂指针是其指向的内存可能已经被分配给其它持有者。相比之下,在 Rust 中编译器确保引用永远也不会变成悬垂状态:当你拥有一些数据的引用,编译器确保数据不会在其引用之前离开作用域。

当我们不小心创建了悬垂引用,Rust在编译的时候就会抛出异常:

fn main() {let reference_to_nothing = dangle();
}fn dangle() -> &String {  // dangle 返回一个字符串的引用let s = String::from("hello");  // s 是一个新字符串&s  // 返回字符串 s 的引用
}// 这里 s 离开作用域并被丢弃。其内存被释放。

因为 s是在 dangle函数内创建的,当 dangle的代码执行完毕后,s将被释放。不过我们尝试返回它的引用。这意味着这个引用会指向一个无效的 String,所以在编译时Rust就会抛出异常,解决方式就是直接返回String

fn no_dangle() -> String {let s = String::from("hello");s
}  // 所有权被移动出去,内存没有被释放


http://www.ppmy.cn/news/60734.html

相关文章

Linux安装MongoDB数据库,并内网穿透远程连接

文章目录 前言1. 配置Mongodb源2. 安装MongoDB3. 局域网连接测试4. 安装cpolar内网穿透5. 配置公网访问地址6. 公网远程连接7. 固定连接公网地址8. 使用固定地址连接 转载自Cpolar Lisa文章:Linux服务器安装部署MongoDB数据库 - 无公网IP远程连接「内网穿透」 前言 …

Redis集合底层实现原理

目录 本章重点简单动态字符串SDS集合底层实现原理zipListlistPackskipListquickListKey 与Value中元素的数量 本章重点 掌握Redis简单动态字符串了解Redis集合底层实现原理 简单动态字符串SDS SDS简介 我们Redis中无论是key还是value其数据类型都是字符串.我们Redis中的字符…

java合并数组的方法

在 Java中,数组是一种重要的数据结构,在 Java中数组的操作方式有两种,一种是直接使用数组来操作,另一种是通过引用计数或者双指针对数组进行操作。对于直接使用数组来操作的方式,我们可以通过两个方法来实现。 一种是将…

数据结构之队列的详解

文章目录 一.什么是队列二.队列的使用2.1 队列的基本操作2.2 队列的基本使用 三.队列的模拟实现3.1 数组实现队列3.2 链表实现队列 四.队列的应用4.1 设计循环队列4.2 设计双端队列4.3 队列实现栈4.4 栈实现队列 五.总结 一.什么是队列 队列是一种先入先出(FIFO)的线性表数据结…

【Linux内核解析-linux-5.14.10-内核源码注释】内核源码中宏定义理解

内核宏定义1 这是Linux内核中的start_kernel函数的一部分代码。它的作用是初始化内核的一些基本组件和数据结构。 asmlinkage: 这是一个函数声明修饰符,指示编译器把函数参数放在堆栈中,而不是寄存器中。 __visible: 这是另一个函数声明修饰符&#x…

使用Linux运维常识

一.基础操作 1.终端常用快捷键 快捷键描述ctrl键盘左键向左跳一个单词ctrl键盘右键向右跳一个单词Ctrl c停止当前正在运行的命令。Ctrl z将当前正在运行的命令放入后台并暂停它的进程。Ctrl d关闭当前终端会话。Ctrl l清屏,也可以用clear命令实现Tab自动补全当…

文字的显示

文字的显示 文章目录 文字的显示1.文字编码方式2.英文和汉字的点阵显示3.显示中文“中”和“A”show_font.c结果 1.文字编码方式 数字>代表什么->显示为什么 GBK国标拓展 下列代码用不同编码方式保存utf-8.c ansi.c #include <stdio.h>int main(int argc ,char *…

05-Vue技术栈之使用脚手架

1、初始化脚手架 1.1 说明 Vue 脚手架是 Vue 官方提供的标准化开发工具&#xff08;开发平台&#xff09;。最新的版本是 5.x。文档: https://cli.vuejs.org/zh/ 1.2 具体步骤 第一步&#xff08;仅第一次执行&#xff09;&#xff1a;全局安装vue/cli npm install -g vue…