生信小白学Rust-03
语句和表达式
举个栗子🌰
fn add_with_extra(x: i32, y: i32) -> i32 {let x = x + 1; // 语句let y = y + 5; // 语句x + y // 表达式
}
// 语句执行操作
// 表达式会返回一个值
怎么区分呢,目前我的理解是只要返回了值,那它就是表达式
fn main() {let y = {let x = 3;x + 1};println!("The value of y is: {}", y);
}
其中,这部分内容也是表达式:
{let x=3;x+1
}
最后一行返回了x+1
的值,需要注意的是x+1不能以分号结尾,否则会从表达式变成语句,表达式不能包含分号。
let a = 8;
let b: Vec<f64> = Vec::new();
let (a, c) = ("hi", false);// let b = (let a = 8);
// 这种是错的,let是语句,不能将语句赋值给其他值
函数
Rust函数跟其他语言几乎没什么区别,相对轻松,下面先请出重量级函数:
所有权
如何从内存中申请空间来存放程序的运行内容,如何在不需要的时候释放这些空间,成了重中之重,也是所有编程语言设计的难点之一。在计算机语言不断演变过程中,出现了三种流派:
- 垃圾回收机制(GC),在程序运行时不断寻找不再使用的内存,典型代表:Java、Go
- 手动管理内存的分配和释放, 在程序中,通过函数调用的方式来申请和释放内存,典型代表:C++
- 通过所有权来管理内存,编译器在编译时会根据一系列规则进行检查
其中 Rust 选择了第三种,最妙的是,这种检查只发生在编译期,因此对于程序运行期,不会有任何性能上的损失。
栈 (后进先出)
栈按照顺序存储值并以相反顺序取出值,这也被称作后进先出。想象一下一叠盘子:当增加更多盘子时,把它们放在盘子堆的顶部,当需要盘子时,再从顶部拿走。不能从中间也不能从底部增加或拿走盘子!
增加数据叫做进栈,移出数据则叫做出栈。
因为上述的实现方式,栈中的所有数据都必须占用已知且固定大小的内存空间,假设数据大小是未知的,那么在取出数据时,你将无法取到你想要的数据。
堆
与栈不同,对于大小未知或者可能变化的数据,我们需要将它存储在堆上。
当向堆上放入数据时,需要请求一定大小的内存空间。操作系统在堆的某处找到一块足够大的空位,把它标记为已使用,并返回一个表示该位置地址的指针,该过程被称为在堆上分配内存,有时简称为 “分配”(allocating)。
接着,该指针会被推入栈中,因为指针的大小是已知且固定的,在后续使用过程中,你将通过栈中的指针,来获取数据在堆上的实际内存位置,进而访问该数据。
由上可知,堆是一种缺乏组织的数据结构。想象一下去餐馆就座吃饭:进入餐馆,告知服务员有几个人,然后服务员找到一个够大的空桌子(堆上分配的内存空间)并领你们过去。如果有人来迟了,他们也可以通过桌号(栈上的指针)来找到你们坐在哪。
处理器在栈上分配数据会比堆更加高效
所有权原则
- Rust 中每一个值都被一个变量所拥有,该变量被称为值的所有者
- 一个值同时只能被一个变量所拥有,或者说一个值只能拥有一个所有者
- 当所有者(变量)离开作用域范围时,这个值将被丢弃(drop)
变量作用域
作用域是一个变量在程序中有效的范围,假如有这样一个变量:
let s = "hello";
变量 s 绑定到了一个字符串字面值,该字符串字面值是硬编码到程序代码中的。s 变量从声明的点开始直到当前作用域的结束都是有效的:
#![allow(unused)]
fn main() {
{ // s 在这里无效,它尚未声明let s = "hello"; // 从此处起,s 是有效的// 使用 s
} // 此作用域已结束,s不再有效
}
简而言之,s 从创建开始就有效,然后有效期持续到它离开作用域为止,可以看出,就作用域来说,Rust 语言跟其他编程语言没有区别。
变量绑定背后的数据交互
好复杂😵😵😵,晕晕乎乎的
转移所有权
Rust基本类型都是通过自动拷贝方式进行赋值,不涉及所有权的转换。
当变量离开作用域后,Rust 会自动调用 drop 函数并清理变量的堆内存。不过由于两个 String 变量指向了同一位置。这就有了一个问题:当 s1 和 s2 离开作用域,它们都会尝试释放相同的内存。这是一个叫做 二次释放(double free) 的错误,也是之前提到过的内存安全性 BUG 之一。两次释放(相同)内存会导致内存污染,它可能会导致潜在的安全漏洞。
因此,Rust 这样解决问题:当 s1 被赋予 s2 后,Rust 认为 s1 不再有效,因此也无需在 s1 离开作用域后 drop 任何东西,这就是把所有权从 s1 转移给了 s2,s1 在被赋予 s2 后就马上失效了。
克隆(深拷贝)
拙见:用clone
方法,将一个变量s1的堆、栈数据全部拷贝给s2,会影响性能。但是对于执行较为频繁的代码(热点路径),使用 clone
会极大的降低程序性能,需要小心使用!
拷贝(浅拷贝)
浅拷贝只发生在栈上,因此性能很高,在日常编程中,浅拷贝无处不在。
拙见:整型之类的基本类型在编译时大小已知,会被存储在栈上。可以理解为在栈上做了深拷贝。
Rust 有一个叫做 Copy
的特征,可以用在类似整型这样在栈中存储的类型。如果一个类型拥有 Copy
特征,一个旧的变量在被赋值给其他变量后仍然可用,也就是赋值的过程即是拷贝的过程。
那么什么类型是可 Copy
的呢?可以查看给定类型的文档来确认,这里可以给出一个通用的规则: 任何基本类型的组合可以 Copy
,不需要分配内存或某种形式资源的类型是可以 Copy
的。如下是一些 Copy
的类型:
- 所有整数类型,比如
u32
- 布尔类型,
bool
,它的值是true
和false
- 所有浮点数类型,比如
f64
- 字符类型,
char
- 元组,当且仅当其包含的类型也都是
Copy
的时候。比如,(i32, i32)
是Copy
的,但(i32, String)
就不是 - 不可变引用
&T
,例如转移所有权中的最后一个例子,但是注意:可变引用&mut T
是不可以 Copy的
函数传值与返回
将值传递给函数,一样会发生 移动
或者 复制
,就跟 let
语句一样,下面的代码展示了所有权、作用域的规则:
fn main() {let s = String::from("hello"); // s 进入作用域takes_ownership(s); // s 的值移动到函数里 ...// ... 所以到这里不再有效let x = 5; // x 进入作用域makes_copy(x); // x 应该移动函数里,// 但 i32 是 Copy 的,所以在后面可继续使用 x} // 这里, x 先移出了作用域,然后是 s。但因为 s 的值已被移走,
// 所以不会有特殊操作fn takes_ownership(some_string: String) { // some_string 进入作用域println!("{}", some_string);
} // 这里,some_string 移出作用域并调用 `drop` 方法。占用的内存被释放fn makes_copy(some_integer: i32) { // some_integer 进入作用域println!("{}", some_integer);
} // 这里,some_integer 移出作用域。不会有特殊操作
引用与借用
引用与解引用
常规引用是一个指针类型,指向了对象存储的内存地址。在下面代码中,我们创建一个 i32
值的引用 y
,然后使用解引用运算符来解出 y
所使用的值:
fn main() {let x = 5;let y = &x;assert_eq!(5, x);assert_eq!(5, *y);
}
变量 x
存放了一个 i32
值 5
。y
是 x
的一个引用。可以断言 x
等于 5
。然而,如果希望对 y
的值做出断言,必须使用 *y
来解出引用所指向的值(也就是解引用)。一旦解引用了 y
,就可以访问 y
所指向的整型值并可以与 5
做比较。
不可变引用
下面的代码,我们用 s1
的引用作为参数传递给 calculate_length
函数,而不是把 s1
的所有权转移给该函数:
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
的参数s
类型从String
变为&String
这里,&
符号即是引用,它们允许你使用值,但是不获取所有权,如图所示:
通过 &s1
语法,我们创建了一个指向 s1
的引用,但是并不拥有它。因为并不拥有这个值,当引用离开作用域后,其指向的值也不会被丢弃。
同理,函数 calculate_length
使用 &
来表明参数 s
的类型是一个引用:
fn calculate_length(s: &String) -> usize { // s 是对 String 的引用s.len()
} // 这里,s 离开了作用域。但因为它并不拥有引用值的所有权,// 所以什么也不会发生
如果尝试修改借用的变量呢?
fn main() {let s = String::from("hello");change(&s);
}fn change(some_string: &String) {some_string.push_str(", world");
}// 报错:
// error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&` reference
// --> /home/cloudlab/main.rs:8:5
// |
// 8 | some_string.push_str(", world");
// | ^^^^^^^^^^^ `some_string` is a `&` reference, so the data it refers to cannot be borrowed as mutable
// |
// help: consider changing this to be a mutable reference
// |
// 7 | fn change(some_string: &mut String) {
// | +++// error: aborting due to 1 previous error// For more information about this error, try `rustc --explain E0596`.
// -bash: line 1: /home/cloudlab/main: No such file or directory// === 代码执行出错 ===
正如变量默认不可变一样,引用指向的值默认也是不可变的,没事,来一起看看如何解决这个问题。
可变引用
根据给出的报错信息进行小小的修改:
fn main() {let mut s = String::from("hello");change(&mut s);
}fn change(some_string: &mut String) {some_string.push_str(", world");
}
首先,声明 s
是可变类型,其次创建一个可变的引用 &mut s
和接受可变引用参数 some_string: &mut String
的函数。
可变引用同时只能存在一个
不过可变引用并不是随心所欲、想用就用的,它有一个很大的限制: 同一作用域,特定数据只能有一个可变引用:
let mut s = String::from("hello");let r1 = &mut s;
let r2 = &mut s;println!("{}, {}", r1, r2);// 报错:
// error[E0499]: cannot borrow `s` as mutable more than once at a time 同一时间无法对 `s` 进行两次可变借用
// --> 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 第一个借用在这里使用
这段代码出错的原因在于,第一个可变借用 r1
必须要持续到最后一次使用的位置 println!
,在 r1
创建和最后一次使用之间,我们又尝试创建第二个可变借用 r2
。
对于新手来说,这个特性绝对是一大拦路虎,也是新人们谈之色变的编译器 borrow checker
特性之一,不过各行各业都一样,限制往往是出于安全的考虑,Rust 也一样。
这种限制的好处就是使 Rust 在编译期就避免数据竞争,数据竞争可由以下行为造成:
- 两个或更多的指针同时访问同一数据
- 至少有一个指针被用来写入数据
- 没有同步数据访问的机制
数据竞争会导致未定义行为,这种行为很可能超出我们的预期,难以在运行时追踪,并且难以诊断和修复。而 Rust 避免了这种情况的发生,因为它甚至不会编译存在数据竞争的代码!
很多时候,大括号可以帮我们解决一些编译不通过的问题,通过手动限制变量的作用域:
let mut s = String::from("hello");{let r1 = &mut s;} // r1 在这里离开了作用域,所以我们完全可以创建一个新的引用let r2 = &mut s;
可变引用与不可变引用不能同时存在
let mut s = String::from("hello");let r1 = &s; // 没问题
let r2 = &s; // 没问题
let r3 = &mut s; // 大问题println!("{}, {}, and {}", r1, r2, r3);
其实这个也很好理解,正在借用不可变引用的用户,肯定不希望他借用的东西,被另外一个人莫名其妙改变了。多个不可变借用被允许是因为没有人会去试图修改数据,每个人都只读这一份数据而不做修改,因此不用担心数据被污染。
悬垂引用
借用规则总结
总的来说,借用规则如下:
- 同一时刻,你只能拥有要么一个可变引用,要么任意多个不可变引用
- 引用必须总是有效的
相关文章:

生信小白学Rust-03
语句和表达式 举个栗子🌰 fn add_with_extra(x: i32, y: i32) -> i32 {let x x 1; // 语句let y y 5; // 语句x y // 表达式 } // 语句执行操作 // 表达式会返回一个值 怎么区分呢,目前我的理解是只要返回了值,那它就是表达式 fn…...

缺乏需求优先级划分时,如何合理分配资源?
当需求优先级不明确时,合理分配资源的关键在于建立统一评估标准、实施敏捷资源管理、提升团队协作效率、加强跨部门沟通机制。尤其是建立统一评估标准至关重要,它能帮助组织快速判断各项需求的重要性与紧迫性,从而实现资源的动态匹配与有效利…...

操作系统学习笔记第3章 内存管理(灰灰题库)
1. 单选题 某页式存储管理系统中,主存为 128KB,分成 32 块,块号为 0、1、2、3、…、31。某作业有 5 块,其页号为 0、1、2、3、4,被分别装入主存的 3、8、4、6、9 块中。有一逻辑地址为 [3, 70](其中方括号中…...

详细分析python 中的deque 以及和list 的用法区别
dqque :双端队列,可以快速的从另外一侧追加和推出对象,deque是一个双向链表,针对list连续的数据结构插入和删除进行优化。它提供了两端都可以操作的序列,这表示在序列的前后你都可以执行添加或删除操作。 通过上图可以看出,deque …...

Stack overflow
本文来源 :腾讯元宝 Stack Overflow - Where Developers Learn, Share, & Build Careers 开发者学习,分享 通过学习、工作和经验积累等方式,逐步建立和发展自己的职业生涯。 Find answers to your technical questions and help othe…...

和为target问题汇总
文章目录 习题377.组合总和 IV494.目标和 和为target的问题,可以有很多种问题的形式的考察,当然,及时的总结与回顾有利于我们熟练掌握这些知识! 习题 377.组合总和 IV 377.组合总和 IV 思路分析:通过观察࿰…...

STM32单片机内存分配详细讲解
单片机的内存无非就两种,内部FLASH和SRAM,最多再加上一个外部的FLASH拓展。在这里我以STM32F103C8T6为例子讲解FLASH和SRAM。 STM32F103C8T6具有64KB的闪存和20KB的SRAM。 一. Flash 1.1 定义 非易失性存储器,即使在断电后,其所…...
RedHat7 如何更换yum镜像源
RedHat7如何更换yum镜像源? # 删除系统自带 yum rpm -qa|grep -e yum -e python-urlgrabber |xargs rpm -e --nodeps# 下载yum与wget的rpm软件包 curl -O http://mirrors.aliyun.com/centos/7/os/x86_64/Packages/yum-3.4.3-168.el7.centos.noarch.rpm curl -O ht…...

Ubuntu 编译SRS和ZLMediaKit用于视频推拉流
SRS实现视频的rtmp webrtc推流 ZLMediaKit编译生成MediaServer实现rtsp推流 SRS指定某个固定网卡,修改程序后重新编译 打开SRS-4.0.0/trunk/src/app/srs_app_rtc_server.cpp,在 232 行后面添加: ZLMediaKit编译后文件存放在ZLMediakit/rele…...

Intellij报错:the file size(3.47M) exceeds configured limit (2.56MB)
今天在部署一个教学平台的时候,当执行数据库脚本出现了以上问题。 自己把解决的方案分享给大家: 于IntelliJ IDEA或PyCharm,可以通过编辑idea.properties文件来增加文件大小限制。 打开idea.properties文件,通常位于IDE的安装目录…...
大数据技术全景解析:Spark、Hadoop、Hive与SQL的协作与实战
引言:当数据成为新时代的“石油” 在数字经济时代,数据量以每年50%的速度爆发式增长。如何高效存储、处理和分析PB级数据,成为企业竞争力的核心命题。本文将通过通俗类比场景化拆解,带你深入理解四大关键技术:Hadoop、…...
软考软件评测师——软件工程之系统维护
一、系统质量属性 可维护性 衡量软件系统适应修改的难易程度,包括修复缺陷、扩展功能或调整规模的效率。计算公式为:系统可用时间占比 1/(1平均修复时间),其中平均修复时间(MTTR)指排除故障所需的平均耗时。 可靠性 vs 可用性 可靠性&…...

Unity动画与生命周期函数
一、Animator动画组件 Animator组件是Unity中用于管理和控制动画的主要工具,它可以处理复杂的动画状态机和动画片段之间的过 1.动画状态机 Animator组件的核心是动画状态机,它由多个动画状态和状态之间的过渡组成。可以通过Unity的动画窗口来创建和编辑…...
合并两个有序数组的高效算法详解
合并两个有序数组的高效算法详解 **合并两个有序数组的高效算法详解****1. 问题描述****2. 常见解法分析****方法 1:合并后排序(暴力法)****方法 2:双指针法(额外空间)** **3. 最优解法:双指针从…...
虚拟Python 环境构建器virtualenv安装(macOS版)
前提 之前我们使用pyenv安装好了Python 3.13.3,并且,全局都使用这个版本的python。现在我们来安装virtualenv。 pipx安装 brew install pipx pipx ensurepath sudo pipx ensurepath --global # optional to allow pipx actions with --global argumen…...

解决ubuntu20中tracker占用过多cpu,引起的风扇狂转
track是linux中的文件索引工具,ubuntu18之前是默认不安装的,所以在升级到20后会默认安装,它是和桌面程序gnome绑定的,甚至还有很多依赖项,导致无法删除,一旦删除很多依赖项都不能运行,禁用也很难…...

在线文档管理系统 spring boot➕vue|源码+数据库+部署教程
📌 一、项目简介 本系统采用Spring Boot Vue ElementUI技术栈,支持管理员和员工两类角色,涵盖文档上传、分类管理、公告发布、员工资料维护、部门岗位管理等核心功能。 系统目标是打造一个简洁高效的内部文档管理平台,便于员工…...

在UI 原型设计中,交互规则有哪些核心要素?
在UI 原型设计中,交互规则主要有三个核心要素,分别为重要性、原则与实践,具体表现在: 一、交互规则在 UI 原型设计中的重要性 明确交互逻辑:设计阶段制定交互规则,清晰定义界面元素操作响应。 如社交应用…...
CSS图片垂直居中问题解决方案
在 CSS 中,使用 vertical-align: middle 导致图片略微向下偏移的现象,本质上是由于 行内元素的基线对齐规则 和 父容器上下文环境 共同作用的结果。以下是具体原因和解决方案: 原因详解 1. vertical-align: middle 的真实含义 该属性 不会让…...
STC8H系列单片机STC8H_H头文件功能注释
#ifndef __STC8H_H__ // 条件编译:如果未定义__STC8H_H__宏 #define __STC8H_H__ // 则定义该宏,防止头文件被重复包含 / //包含本头文件后,不用另外再包含"REG51.H" // 提示:本头文件已包含基本寄存器定义 sfr P0 = …...
Python类的力量:第五篇:魔法方法与协议——让类拥有Python的“超能力”
文章目录 前言:从“普通对象”到“Python原生公民”的进化之路 一、魔法方法:赋予对象“超能力”的基因1. 构造与析构:对象生命周期的“魔法开关”2. 字符串表示:对象的“自我介绍”3. 运算符重载:让对象支持“数学魔法…...

OpenResty Manager 介绍与部署(Docker部署)
概述 OpenResty-Manager 是一个基于 OpenResty 构建的开源 Web 管理平台。OpenResty 是一个高性能的 Web 平台,集成了 Nginx 和 LuaJIT,支持强大的脚本功能。OpenResty-Manager 由 Safe3 开发,提供了一个用户友好的界面,用于管理…...
深入解析HTTP协议演进:从1.0到3.0的全面对比
HTTP协议作为互联网的基础协议,经历了多个版本的迭代演进。本文将详细解析HTTP 1.0、HTTP 1.1、HTTP/2和HTTP/3的核心特性与区别,帮助开发者深入理解网络协议的发展脉络。 一、HTTP 1.0:互联网的奠基者 核心特点: 短连接模式&am…...

快速搭建一个electron-vite项目
1. 初始化项目 在命令行中运行以下命令 npm create quick-start/electronlatest也可以通过附加命令行选项直接指定项目名称和你想要使用的模版。例如,要构建一个 Electron Vue 项目,运行: # npm 7,需要添加额外的 --: npm cre…...
【Android】Android 实现一个依赖注入的注解
Android 实现一个依赖注入的注解 🎯 目标功能 自定义注解 Inject创建一个 Injector 类,用来扫描并注入对象支持 Activity 或其他类中的字段注入 🧩 步骤一:定义注解 import java.lang.annotation.ElementType; import java.lan…...

unity terrain 在生成草,树,石头等地形障碍的时候,无法触发碰撞导致人物穿过模型
1.terrain地形的草,石头之类要选择模型预制体 2.在人物身上挂碰撞器和刚体,或者单挂一个character controller组件也行 3.在预制体上挂碰撞盒就好了,挂载meshcollider会导致碰撞无效...
使用gitbook 工具编写接口文档或博客
步骤一:在项目目录中初始化一个 GitBook 项目 mkdir mybook && cd mybook git init npm init -y步骤二:添加书籍结构(如 book.json, README.md) echo "# 我的书" > README.md echo "{}" > bo…...

75.xilinx复数乘法器IP核调试
(83*j)*(57j) 935j 正确的是 1971j 分析出现的原因:(abj)* (cdj) (ac-bd)j(adbc) 其中a,b,c,d都是16bit的有符号数,乘积的结果为保证不溢出需要32bit存储,最终的复数乘法结果是两个32b…...
软件工程之软件产品的环境
比较正规的做法是分下面的三个 1.开发环境(Development Environment): 用途:这是软件开发人员编写和测试代码的地方。在开发环境中,开发者可以自由地试验、调试代码,以及进行初步的功能实现和测试。 特点&…...

8.ADC
目录 ADC 模拟信号和数字信号的区别和区别 信号的区别 如何采集信号 常见的接口 数字接口 模拟接口 ADC 实际应用 ADC 转换器的定义 ADC 相关的名词 ADC 采集的原理 ADC 的参考电压 相关的计算 如何实现 ADC STM32 内的 ADC 转换器讲解 STM32 的 ADC 简介 AD…...