WHAT - 高性能和内存安全的 Rust(二)

目录

  • 1. 所有权(Ownership)
  • 2. 借用(Borrowing)
    • 不可变借用
    • 可变借用
  • 3. 可变性(Mutability)
  • 4. 作用域(Scope)
  • 综合示例

了解 Rust 的所有权(ownership)、借用(borrowing)、可变性(mutability)以及作用域(scope)是掌握 Rust 的关键。

下面通过具体的代码示例来解释这些概念。

1. 所有权(Ownership)

所有权和生命周期是 Rust 最大的特性之一,理解了也就掌握了 80% Rust 的精髓。

  • Rust 中的每一个值都有一个所有者,某个特定的变量
  • Rust 中任何一个值在任何时刻有且只有一个所有者
  • 当所有者离开作用域,值即被丢弃

以下示例展示了基本的所有权规则:

fn main() {let s1 = String::from("hello");let s2 = s1; // s1的所有权被移动到s2// println!("{}", s1); // 这行代码会报错,因为s1不再拥有这个值的所有权println!("{}", s2); // s2现在拥有这个值
}

在这个例子中,s1将其所有权移动到s2,因此s1不再有效。可以尝试在 Rust 在线工具 中运行。

首先要明确值的概念,上述例子中,字符串在堆上被分配的空间以及内容才是真正的值,而 s1s2 是指向这个值的指针,也就是所有者。由于任何一个值在任何时刻有且只有一个所有者,当执行 let s2 = s1; 后,s1 不再拥有值的所有权,因此编译会报错。

如此编译错误也可以避免生产环境的 Use-after-free 漏洞。

再来看另一个例子:

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

上述代码会编译错误。可以尝试在 Rust 在线工具 中运行。

问题出来 calc_length,执行该函数时,值的所有权已经有 s1 移交给 s,而因为当所有者离开作用域后对应的值会被丢弃,s 所指向的值就被丢弃了。

想要避免这个编译错误,我们需要把所有权移交出去:

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

很不幸,上述代码会有新的编译错误。问题出在 (s, s.len());,因为任何一个值在任何时刻有且只有一个所有者,所有执行完前面一半代码后,(s, s 的所有权已经移交出去了,再执行后一半,s.len()),就会报错,因为 s 在这一刻就不存在了。

继续优化代码:

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

2. 借用(Borrowing)

上述第二个例子的代码出现一个问题:只想要获取字符串长度,却需要将所有权转移两次。因此,Rust 提供了另外一个重要的概念:借用。

借用是指通过引用传递数据,而不是通过值传递。Rust 区分可变借用和不可变借用。

不可变借用

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() // 借用s,不会改变所有权
}

在这个例子中,calculate_length函数借用了s1的不可变引用,因此calculate_length函数不能修改s1的值。

可变借用

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

在这个例子中,change函数借用了s的可变引用,因此可以修改s的值。

由于 Rust 对所有权以及引用有严格的控制,因此 Rust 在任何时间可以准确知晓引用的数量,因此 Rust 可以对引用使用类似读锁(共享锁)以及写锁(排他锁)的控制。换句话说,Rust 中的任何值:

  1. 在任意时间,要么只有一个可变引用(排他锁),要么只能有多个不可变引用(共享锁)
  2. 引用必须总是有效的

上述特性在多线程编程尤为重要,可以很大程度保证代码的线程安全,并且由于这些分析发生在编译时,Rust 可以避免为了保证线程安全带来的额外性能开销。

3. 可变性(Mutability)

Rust 中变量默认是不可变的,需要显式地使用 mut 关键字声明可变变量。

fn main() {let mut x = 5;println!("The value of x is: {}", x);x = 6;println!("The value of x is: {}", x);
}

在这个例子中,x被声明为可变的,因此可以修改其值。

4. 作用域(Scope)

作用域决定了变量在程序中的生命周期。变量一旦离开作用域,其值会被自动销毁。

Rust 和大部分编程语言类似,值的作用域是离它的声明最近的一对花括号中间。

fn main() {{let s = String::from("hello"); // s在这个作用域内有效println!("{}", s);} // 这里s离开作用域并被销毁// println!("{}", s); // 这行代码会报错,因为s已经超出了作用域
}

在这个例子中,s的作用域仅限于花括号内部,一旦超出作用域,s会被销毁,无法再访问。

综合示例

下面的示例综合展示了所有权、借用、可变性和作用域:

fn main() {let mut s = String::from("hello");{let r1 = &s; // 不可变借用let r2 = &s; // 不可变借用println!("r1: {}, r2: {}", r1, r2);} // r1和r2在这里离开作用域{let r3 = &mut s; // 可变借用r3.push_str(", world");println!("r3: {}", r3);} // r3在这里离开作用域println!("s: {}", s);
}

这个例子展示了如何在不同作用域内进行不可变和可变借用,以及如何在作用域结束时自动释放借用。

通过这些示例,可以更好地理解Rust中的所有权、借用、可变性和作用域的基本概念。

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

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

相关文章

跨平台免费流程图(思维导图)制作工具 draw.io v24.6.3(可离线)

在当今快节奏的工作环境中,有效地传达复杂信息和工作流程至关重要。流程图和思维导图是两种强大的视觉工具,它们帮助我们清晰地表达想法,理解复杂的系统,并协作完成项目。可以帮助我们清晰地展示信息和逻辑关系。然而,…

MySQL学习笔记-进阶篇-锁

概述 概念 全局锁 表级锁 介绍 表锁 读锁 只允许加锁客户端读操作禁止写操作。允许其他客户端的读操作,阻塞其他客户端的写操作。 lock tables xxx read unlock tables; 写锁 允许加锁客户端的读写操作。既阻塞其他客户端的读,又阻塞其他客…

告别繁琐邀请码,Xinstall助你轻松搭建高效App推广体系!

随着互联网流量的不断变迁,App推广和运营面临着前所未有的挑战。如何快速搭建起满足用户需求的运营体系,成为众多企业亟待解决的问题。在这个背景下,Xinstall凭借其强大的功能和灵活的解决方案,成为了App推广的得力助手。 一、传…

【Redis】

Redis 常见面试题 认识 Redis 什么是 Redis? 我们直接看 Redis 官方是怎么介绍自己的。 Redis 官方的介绍原版是英文的,我翻译成了中文后截图的,所以有些文字读起来会比较拗口,没关系,我会把里面比较重要的特性抽出来…

Centos7.9系统对于文件和文件夹的常用命令操作说明

一、背景描述 在我们的日常工作和生活中,会遇到需要连接Linux服务器操作的情况,最常遇到的一些操作就是文件和文件夹的操作,将这些常用操作记录一下,方便后续的使用。 二、文件常用操作指令 Linux下的指令可以通过按下【tab】键进…

SpringBoot使用spring.factories加载默认配置

在日常开发过程中,发布一些产品或者框架时,会遇到某些功能需要一些配置才能正常运行,这时我们需要的提供默认配置项,同时用户也能覆盖进行个性化 创建Initializer public class FrameContextInitializer implements Application…

VUE 组件之间的传值

通过做过的项目,目前学到几种组件之间的传值方法,留个笔记,才发现,其实不管是组件之间,还是父子组件之间,传值方法挺多的呢。 (1)模板引用 defineExpose 官网简介:模板…

5.3 Python len()函数:获取字符串长度或字节数

Python len()函数详解:获取字符串长度或字节数 Python 中,要想知道一个字符串有多少个字符(获得字符串长度),或者一个字符串占用多少个字节,可以使用 len 函数。 len 函数的基本语法格式为: …

ElasticSearch7.17.19集群搭建+Kibana

下载 1、下载elasticSearch 官网:https://www.elastic.co/cn/downloads/elasticsearch解压 tar -zxvf elasticsearch-7.17.19-linux-x86_64.tar### 修改用户组,es不允许root用户启动 chown es:es elaticsearch### 修改elasticsearch.yml # 所有节点名…

Unity - DrawCall

CPU 向 GPU 发送指令来渲染一个或多个物体的过程。每个 DrawCall 都会触发 GPU 执行一次绘制操作,包括设置渲染状态、传递顶点数据和纹理等。DrawCall 的增加会导致渲染性能下降 合批:将多个物体合并为一个批次进行渲染。其中分为静态合批和动态合批。 …

ConcurrentModificationException (异常通常是由于在一个线程遍历集合的同时,另一个线程修改了集合),解决方案如下

ArrayList 不是线程安全的集合。如果多个线程可能同时访问和修改ArrayList,可以考虑以下几种方法来解决这个问题:1、使用线程安全的集合: 使用 CopyOnWriteArrayList 或者 Collections.synchronizedList 包装的 ArrayList。 代码如下: private final BlockingQueue<List…

如何从docker容器中导入或者导出文件

在Docker中&#xff0c;从容器中导入或导出文件通常可以通过以下几种方法实现&#xff1a; 从容器中导出文件 使用 docker cp 命令 这是最直接的方法&#xff0c;可以从容器内部复制文件到宿主机的文件系统&#xff1a; docker cp <容器ID或容器名称>:<容器内的文…

双指针算法专题(移动零 复写零 快乐数)

目录 前言 1. 移动零 &#xff08;1&#xff09;题目及示例 &#xff08;2&#xff09;一般思路 &#xff08;3&#xff09;双指针解法 2. 复写零 &#xff08;1&#xff09;题目及示例 &#xff08;2&#xff09;一般解法 &#xff08;3&#xff09;双指针解法 3. 快…

在Mac OS上使用Visual Studio Code创建C++ Qt的Hello World应用

引言 Qt是一个跨平台的应用程序和用户界面框架&#xff0c;而Visual Studio Code是一个功能强大的编辑器&#xff0c;两者结合可以极大地提升开发效率。本文将指导你在Mac OS上使用Visual Studio Code创建一个简单的Qt Hello World窗口应用。 环境准备 确保你的MacBook OS运行…

Effective C++ 改善程序与设计的55个具体做法笔记与心得 5

五. 实现 26. 尽可能延后变量定义式的出现时间 请注意: ‌‌‌‌  尽可能延后变量定义式的出现&#xff0c;这样做可增加程序的清晰度并改善程序效率。 解释&#xff1a; 增加程序的清晰度&#xff1a;这样可以让读者在第一时间内更好地理解变量的用途。变量定义在第一次使…

黑马苍穹外卖6 清理redis缓存+Spring Cache+购物车的增删改查

缓存菜品 后端服务都去查询数据库&#xff0c;对数据库访问压力增大。 解决方式&#xff1a;使用redis来缓存菜品&#xff0c;用内存比磁盘性能更高。 key :dish_分类id String key “dish_” categoryId; RestController("userDishController") RequestMapping…

批量重命名神器揭秘:一键实现文件夹随机命名,自定义长度轻松搞定!

在数字化时代&#xff0c;我们经常需要管理大量的文件夹&#xff0c;尤其是对于那些需要频繁更改或整理的文件来说&#xff0c;给它们进行批量重命名可以大大提高工作效率。然而&#xff0c;传统的重命名方法既繁琐又耗时&#xff0c;无法满足高效工作的需求。今天&#xff0c;…

transformers datasets

☆ 问题描述 在进行自然语言处理项目时&#xff0c;经常需要加载和处理不同的数据集。为了简化这一过程&#xff0c;我们可以使用datasets库来方便地加载、切分、查看和处理数据。本解决方案提供了如何使用datasets库加载、查看和处理数据的详细示例&#xff0c;包括如何加载在…

Java中的类加载器与热部署技术详解

Java中的类加载器与热部署技术详解 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;在软件开发中&#xff0c;特别是在大型应用和服务的开发过程中&#xff0c;类…

ic基础|功耗篇03:ic设计人员如何在代码中降低功耗?一文带你了解行为级以及RTL级低功耗技术

大家好&#xff0c;我是数字小熊饼干&#xff0c;一个练习时长两年半的ic打工人。我在两年前通过自学跨行社招加入了IC行业。现在我打算将这两年的工作经验和当初面试时最常问的一些问题进行总结&#xff0c;并通过汇总成文章的形式进行输出&#xff0c;相信无论你是在职的还是…