This commit is contained in:
Davirain
2023-05-17 17:34:42 +08:00
parent 60ea71a47c
commit 7ff4ad4d46
9 changed files with 263 additions and 190 deletions

View File

@@ -1,72 +1,83 @@
## 3.1 变量和可变性
### 3.1.1 绑定
在Rust中将值和变量关联的过程称为绑定变量的绑定可以使用let关键字如下
# 3.1 变量和可变性
## 3.1.1 绑定
在Rust中将值和变量关联的过程称为绑定变量的绑定可以使用`let`关键字,如下:
```Rust
let a = 1; // 将1绑定到变量a
let mut b = 2; // 将2绑定到变量b
let some_number = Some(2); // 将Some(2)绑定到some_number
```
### 3.1.2 变量
## 3.1.2 变量
Rust中变量分为不可变变量和可变变量。不可变变量不能对其进行二次绑定可变变量可以对其进行二次绑定。
- 不可变变量定义方式如下:
```Rust
let a: u32 = 1; //将1绑定到a这个变量
let b = 0u32;
let c = 1; //定义时不指定类型,可以自动类型推导
```
对不可变变量二次绑定一个值会报错:
```Rust
let a: u32 = 1; //将1绑定到变量aa为不可变变量
a = 2; //编译错误a是不可变的变量不能进行二次绑定
```
```Rust
let a: u32 = 1; //将1绑定到a这个变量
let b = 0u32;
let c = 1; //定义时不指定类型,可以自动类型推导
```
对不可变变量二次绑定一个值会报错:
```Rust
let a: u32 = 1; //将1绑定到变量aa为不可变变量
a = 2; //编译错误a是不可变的变量不能进行二次绑定
```
- 可变变量定义方式如下:
```Rust
let mut a: u32 = 1; //通过mut关键字定义可变变量
a = 2; //将2绑定到变量a编译正确因为a是可变变量可以进行二次绑定
let mut b = 2;
b = 3;
```
```Rust
let mut a: u32 = 1; //通过mut关键字定义可变变量
a = 2; //将2绑定到变量a编译正确因为a是可变变量可以进行二次绑定
let mut b = 2;
b = 3;
```
设计思考:
从编译器的角度如果一个值定义为不可变变量那它就不会改变更易于推导。想想一下如果代码非常多如果变量不会变化但是允许它可变其实会更容易滋生bug。
### 3.1.3常量
常量是绑定到一个名称不允许改变的值,定义方式如下:
## 3.1.3常量
常量是绑定到一个名称不允许改变的值,定义方式如下:
```Rust
const HOUR_IN_SECONDS: u32 = 60 * 60;
```
常量和不可变变量的区别:
- 不允许对常量使用mut关键字它总是不可变的定义时必须显式的标注类型
```Rust
let a = 1u32; //编译正确
let a = 1; //编译正确
const A: u32 = 1; //编译正确
const B = 2u32; //编译错误
const C = 2; //编译错误
```
```Rust
let a = 1u32; //编译正确
let a = 1; //编译正确
const A: u32 = 1; //编译正确
const B = 2u32; //编译错误
const C = 2; //编译错误
```
- 常量可以在任何作用域声明,包括全局作用域;
- 常量只能被设置为常量表达式,不能是在运行时计算出来的值。
```Rust
let a: u32 = 1;
let b: u32 = 2;
const A: u32 = a + b; //编译错误
```
```Rust
let a: u32 = 1;
let b: u32 = 2;
const A: u32 = a + b; //编译错误
```
### 3.1.4 隐藏
## 3.1.4 隐藏
Rust中可以定义一个与之前的变量同名的变量这称之为第一个变量被第二个变量隐藏。隐藏和`mut`的区别隐藏是定义了一个新的变量而使用mut是修改原来的变量。
Rust中可以定义一个与之前的变量同名的变量这称之为第一个变量被第二个变量隐藏。隐藏和mut的区别隐藏是定义了一个新的变量而使用mut是修改原来的变量。
```Rust
fn main() {
let a: u32 = 1; //这个变量a被下面的a隐藏掉了
let a: u32 = 2; //定义了一个新的变量这个变量也叫作a
println!("a: {:?}", a); //输出结果为2
let mut b: u32 = 1; //定义可变变量b
b = 2; //对b的值进行的修改
println!("b: {:?}", b); //输出结果为2

View File

@@ -1,9 +1,10 @@
## 3.2 基本数据类型
# 3.2 基本数据类型
Rust是静态类型语言编译时就必须知道所有变量的类型。根据值以及其使用方式Rust编译器通常能自动推导出变量的类型。
Rust有两种数据类型子集分别是标量scalar类型和复合compound类型。
### 3.2.1 标量类型
## 3.2.1 标量类型
标量类型包括:整型、浮点型、布尔类型和字符类型。
- 整型和浮点型
@@ -19,9 +20,9 @@ Rust中的整型和浮点型如下
|128 bit| i128 | u128 | |
| arch | isize | usize | |
说明isizeusize的长度是和平台相关如果CPU是32位的则这两个类型是32位的如果CPU是64位的则这两个类型是64位的。
说明:`isize``usize`的长度是和平台相关如果CPU是32位的则这两个类型是32位的如果CPU是64位的则这两个类型是64位的。
上面的表格中f32f64为浮点型其它为整型。浮点型和整型一起构成数值型。
上面的表格中,`f32``f64`为浮点型,其它为整型。浮点型和整型一起构成数值型。
1可以在数值字面量后面加上类型表示该类型的数值如下
```Rust
@@ -71,103 +72,106 @@ fn main() {
- 布尔型
Rust中的布尔型用bool表示有两个可能的值为true和false。布尔类型使用的场景主要是条件表达式控制流的内容使用如下
```Rust
fn main() {
// 定义方式
let a: bool = true;
let b: bool = false;
Rust中的布尔型用bool表示有两个可能的值为true和false。布尔类型使用的场景主要是条件表达式控制流的内容使用如下
// 使用场景
if a {
println!("a is true");
} else {
println!("a is false");
}
```Rust
fn main() {
// 定义方式
let a: bool = true;
let b: bool = false;
if b {
println!("b is true");
} else {
println!("b is false");
// 使用场景
if a {
println!("a is true");
} else {
println!("a is false");
}
if b {
println!("b is true");
} else {
println!("b is false");
}
}
}
```
```
- 字符类型
char类型用于存放单个unicode字符占用4个字节空间。当存储char类型数据时Rust会将其转换为utf-8编码的数据存储。char字面量是单引号包含的任意单个字符字符类型使用示例如下
```Rust
fn main() {
let c: char = 'z';
let x: char = 'x';
let heart_eyed_cat: char = '😻';
}
```
char类型用于存放单个unicode字符占用4个字节空间。当存储char类型数据时Rust会将其转换为utf-8编码的数据存储。char字面量是单引号包含的任意单个字符字符类型使用示例如下
### 3.2.2. 原生复合类型
```Rust
fn main() {
let c: char = 'z';
let x: char = 'x';
let heart_eyed_cat: char = '😻';
}
```
## 3.2.2. 原生复合类型
复合类型是将多个值组合成一个类型。Rust有两个原生复合类型元组和数组。
- 元组
圆括号以及其中逗号分割的值列表组成元组,定义一个元组方式如下:
```Rust
fn main() {
let tup: (i32, f64, u8) = (500, 6.4, 1);
}
```
可以将元组重新结构到变量上,如下:
```Rust
fn main() {
let tup = (500, 6.4, 1);
let (x, y, z) = tup;
//接下来你可以使用x、y、z
}
```
也可以直接使用元组的元素,如下:
```Rust
fn main() {
let x: (i32, f64, u8) = (500, 6.4, 1);
let first = x.0;
let second = x.1;
let third = x.2;
}
```
圆括号以及其中逗号分割的值列表组成元组,定义一个元组方式如下:
```Rust
fn main() {
let tup: (i32, f64, u8) = (500, 6.4, 1);
}
```
可以将元组重新结构到变量上,如下:
```Rust
fn main() {
let tup = (500, 6.4, 1);
let (x, y, z) = tup;
//接下来你可以使用x、y、z
}
```
也可以直接使用元组的元素,如下:
```Rust
fn main() {
let x: (i32, f64, u8) = (500, 6.4, 1);
let first = x.0;
let second = x.1;
let third = x.2;
}
```
不带任何值的元组称为unit类型单元元组可以代表空值或者空的返回类型如下
```Rust
fn main() {
let x: () = (); // 将值()保存到类型为()的变量x中
}
```
不带任何值的元组称为unit类型单元元组可以代表空值或者空的返回类型如下
```Rust
fn main() {
let x: () = (); // 将值()保存到类型为()的变量x中
}
```
- 数组
数组中的每个元素的类型必须相同,数组的长度是固定的,数组的定义方式如下:
```Rust
fn main() {
let a = [1, 2, 3, 4, 5]; //直接写数组的值
let b: [i32; 5] = [1, 2, 3, 4, 5]; //显示指定数组的类型和长度
let c: [i32; 5] = [3; 5]; //数组每个元素为同样的值等价于let a = [5, 5, 5, 5, 5];
}
```
数组通过索引来访问元素索引从0开始计数如下
```Rust
fn main() {
let a = [1, 2, 3, 4, 5];
数组中的每个元素的类型必须相同,数组的长度是固定的,数组的定义方式如下:
let first = a[0]; // first = 1
let second = a[1]; // second = 2
}
```
Rust中访问无效的索引元素会报错如下
```Rust
fn main() {
let a = [1, 2, 3, 4, 5];
let b = a[5]; // 错误只能放为0-4所以这个代码将无法编译
}
```
```Rust
fn main() {
let a = [1, 2, 3, 4, 5]; //直接写数组的值
let b: [i32; 5] = [1, 2, 3, 4, 5]; //显示指定数组的类型和长度
let c: [i32; 5] = [3; 5]; //数组每个元素为同样的值等价于let a = [5, 5, 5, 5, 5];
}
```
数组通过索引来访问元素索引从0开始计数如下
```Rust
fn main() {
let a = [1, 2, 3, 4, 5];
### 3.2.3 类型转换 (少一个From, Into, TryFrom, TryInto)
let first = a[0]; // first = 1
let second = a[1]; // second = 2
}
```
Rust中访问无效的索引元素会报错如下
```Rust
fn main() {
let a = [1, 2, 3, 4, 5];
let b = a[5]; // 错误只能放为0-4所以这个代码将无法编译
}
```
## 3.2.3 类型转换 (少一个From, Into, TryFrom, TryInto)
Rust中可以使用as进行类型转换。

View File

@@ -1,6 +1,7 @@
## 3.3 函数
# 3.3 函数
## 3.3.1. 函数定义
### 3.3.1. 函数定义
fn关键字、函数名、函数参数名及其类型如果有的话、返回值类型如果有的话组成函数签名 加上由一对花括号包含的函数体组成函数。例子如下:
```Rust
// 一个没有参数,也没有返回值的函数
@@ -50,7 +51,7 @@ fn main() {
}
```
### 3.3.2. 语句和表达式
## 3.3.2. 语句和表达式
Rust中语句是执行一个写操作但不返回值的指令表达式则计算并产生一个值。
```Rust
@@ -66,7 +67,7 @@ fn main() {
}
```
### 3.3.3. 函数返回值
## 3.3.3. 函数返回值
- 使用return指定返回值如下
```Rust

View File

@@ -1,4 +1,4 @@
## 3.4 注释
# 3.4 注释
在Rust中注释分为三类
@@ -8,7 +8,7 @@
本节主要简单介绍代码注释和文档注释,对于注释的其它功能,我们后面再深入。
### 3.4.1. 代码注释
## 3.4.1. 代码注释
代码注释有两种:
@@ -37,7 +37,7 @@ fn main() {
}
```
### 3.4.2.文档注释
## 3.4.2.文档注释
Rust提供了cargo doc命令可以把文档注释转换成html网页最终展示给用户。文档注释也有文档行注释和文档块注释

View File

@@ -1,4 +1,4 @@
## 3.5 控制流
# 3.5 控制流
Rust中的控制流结构主要包括
@@ -7,7 +7,7 @@ Rust中的控制流结构主要包括
- while循环
- for .. in 循环。
### 3.5.1. if条件判断
## 3.5.1. if条件判断
- if执行条件判断示例如下
```Rust
@@ -64,7 +64,7 @@ fn main() {
}
```
### 3.5.2. loop循环
## 3.5.2. loop循环
- loop重复执行代码
```Rust
@@ -126,7 +126,7 @@ fn main() {
}
```
### 3.5.3. while条件循环
## 3.5.3. while条件循环
- while条件循环执行代码当条件不满足后结束循环如下
```Rust
fn main() {
@@ -156,7 +156,7 @@ fn main() {
}
```
### 3.5.4. for .. in 循环
## 3.5.4. for .. in 循环
for循环用来对一个集合的每个元素执行一些代码使用方式如下
```Rust

View File

@@ -1,5 +1,7 @@
## 3.6 Rust内存模型
### 3.6.1 Rust程序内存布局
# 3.6 Rust内存模型
## 3.6.1 Rust程序内存布局
![注释](../../assets/2.png)
上图是一张linux系统上Rust程序的内存布局图。在linux操作系统中会划分固定的区域给内核使用即上图中的内核空间应用程序使用的是用户空间。
@@ -11,8 +13,10 @@ Rust程序使用的内存空间分为如下
- 堆区Heap程序代码中动态分配的内存在程序运行时申请该区域向上增长。
- 栈区Stack该区域存放函数调用的参数、局部变量和返回地址等信息在编译阶段分配向下增长。
### 3.6.2 栈和堆
#### 1.栈和栈帧
## 3.6.2 栈和堆
### 1.栈和栈帧
“栈和栈帧属于操作系统的概念,由操作系统进行管理,栈空间以后进先出的顺序存储数据。将数据放到栈上就做入栈,将数据移出栈就做出栈。
每次调用函数时操作系统会在栈顶创建一个栈帧来保存函数的上下文数据主要是函数内部声明的局部变量函数返回时返回值也会存储在该栈帧中。当函数调用者取得该函数返回值后栈帧会被释放。”引用自《Rust入门秘籍》。
@@ -42,12 +46,12 @@ fn main() {
这里需要注意的是两个帧对应同样的内存地址这是因为在调用完f1函数后其对应的栈帧释放释放的实际意义就是这段内存可以被重新分配了然后调用f2函数为其分配栈帧时从同样的地址进行分配。
#### 2. 堆
### 2. 堆
堆空间和栈空间不同,不由操作系统管理,在需要时申请,不需要时释放。申请和释放堆内存是一件困难的事情,尤其当程序代码较多时。只申请堆内存而不释放会造成内存泄露,内存泄露过多会造成内存耗尽而崩溃。错误的释放在使用的内存也会造成程序运行错误(或直接无法运行)。
有些编程语言使用提供垃圾管理回收器GC来自动回收不再使用的堆内存有些语言必须完全由程序员在代码中手动申请和释放内存。
Rust没有GC但通过其独特的机制管理内存程序员不用手动申请和释放堆内存。
#### 3. Rust如何使用堆和栈
### 3. Rust如何使用堆和栈
栈中存储的所有数据都必须占用(在编译时就)已知且固定的大小。编译时大小未知或可能变化的数据,存储在堆上。
数据存放到栈上时,是直接将数据放到栈内存。
当数据需要存放到堆上时,内存分配器则是根据数据的大小,在堆内存找到合适大小的空区域存放,把它标记为已使用,并返回一个表示该位置地址的指针。该指针存储在栈上,当需要访问具体的数据时,必须先访问指针,然后通过指针找到堆上的位置,从而访问数据。这个过程可以用下图表示:

View File

@@ -1,7 +1,7 @@
### 3.7.1 所有权介绍
# 3.7.1 所有权介绍
所有权是Rust最为与众不同的特性它让Rust无需垃圾回收即可保证内存安全。
#### 1. 所有权规则
## 1. 所有权规则
Rust所有权的规则如下
@@ -16,13 +16,14 @@ fn main() {
}
```
上面的代码中a就是8的所有者b是String::from("hello")的所有者c则是vec![1, 2, 3]的所有者。
上面的代码中a就是`8`的所有者b是`String::from("hello")`的所有者c则是`vec![1, 2, 3]`的所有者。
注意b是String::from("hello")的所有者但是b不是字符串"hello"的所有者。同理c是vec![1, 2, 3]的所有者,但不是[1, 2, 3]的所有者。至于为什么后续内容String类型部分会进行讲解。
注意b是`String::from("hello")`的所有者但是b不是字符串`"hello"`的所有者。同理c是`vec![1, 2, 3]`的所有者,但不是`[1, 2, 3]`的所有者。至于为什么后续内容String类型部分会进行讲解。
#### 2. 变量的作用域
## 2. 变量的作用域
变量作用域是变量在程序中有效的范围。一对花括号表示的范围就是作用域,变量有效的范围就是从创建开始,到离开作用域结束。
示例1
```Rust
fn f() {
let b = 1u32; // --------------------------------|
@@ -44,6 +45,7 @@ fn main() {
```
示例2
```Rust
fn main() {
let a = 8u32; // --------------------------|
@@ -58,13 +60,14 @@ fn main() {
}
```
#### 3. String类型
## 3. String类型
- String类型的创建有下面三种方式
- String::from
- to_string
- String::new
- `String::from`
- `to_string`
- `String::new`
```Rust
fn main() {
let s1 = String::from("Hello"); // 方法一
@@ -87,26 +90,33 @@ fn main() {
- String类型的本质
Rust标准库中String类型的定义如下
```Rust
pub struct String {
vec: Vec<u8>,
}
```
Vec类型的定义如下
```Rust
pub struct Vec<T> {
buf: RawVec<T>,
len: usize, // 长度
}
```
RawVec定义则类似于如下为了更好的说明String类型下面的定义用简化的代码
```Rust
struct RawVec<T> {
ptr: NonNull<T>, // 指针
cap: usize, // 容量
}
```
那对于整个String类型可以用伪代码表示如下
```Rust
struct String {
v: struct Vec<u8> {
@@ -115,7 +125,9 @@ struct String {
}
}
```
更进一步的简化可以得到String类型本质如下
```Rust
struct String {
ptrNonNull<u8>,
@@ -123,11 +135,13 @@ struct String {
len: usize,
}
```
所以String类型本质是三个字段一个指针一个容量大小一个长度大小。
- 内存分配
在Rust中编译时大小确定的数据放在栈上编译时大小不能确定的数据放在堆上。考虑如下代码
```Rust
fn main() {
let mut s = String::new();
@@ -137,13 +151,15 @@ fn main() {
println!("{s}"); // 打印AB
}
```
在第2行定义String类型时并不能确定最终字符串的大小所以字符串内容本身应该存储在堆上。结合什么String类型的本质的内容可以得到String类型的存储如下
![注释](../../assets/5.png)
String类型本身是三个字段指针、长度、容量在编译时是已知的大小存储在栈上String类型绑定的字符串在上面代码中是“AB”在编译时大小未知是运行时在堆上分配内存分配后的内存地址保存在String类型的指针字段中内存大小保存在cap字段中内存上存储的字符串长度保存在len字段中。
#### 4. move语义
## 4. move语义
Rust所有权规则第二条在任意时刻值有且仅有一个所有者。那么当一个变量赋给另外一个变量时发生了什么
- 完全存储在栈上的类型
@@ -156,11 +172,13 @@ fn main() {
println!("x: {:?}, y: {:?}", x, y);
}
```
x和y都是u32类型在编译时知道大小都存储在栈上。代码第2行是将5绑定到变量x上第3行则是通过自动拷贝的方式将5绑定到y上先拷贝x的值5然后将拷贝后得到的5绑定到y上。所以当let y = x发生后这段代码里面最后有两个值5分别绑定到了x和y上。
x和y都是u32类型在编译时知道大小都存储在栈上。代码第2行是将5绑定到变量`x`第3行则是通过自动拷贝的方式将5绑定到`y`上(先拷贝`x`的值5然后将拷贝后得到的5绑定到y上。所以`let y = x`发生后这段代码里面最后有两个值5分别绑定到了`x``y`上。
- 涉及到堆存储的类型
再考虑如下代码:
```Rust
fn main() {
let s = "Hello world!".to_string();
@@ -169,7 +187,7 @@ fn main() {
println!("s1: {:?}", s1);
}
```
s是String类型字符串Hello world!”是存储在堆内存上的,其内存布局如下:
s是`String`类型,字符串`"Hello world"`是存储在堆内存上的,其内存布局如下:
![注释](../../assets/6.png)
@@ -177,27 +195,28 @@ s是String类型字符串“Hello world”是存储在堆内存上的
![注释](../../assets/7.png)
当let s1 = s执行后就发生了所有权的转移String类型值的所有权从s转移到了s1。此时Rust认为原来的s不再有效。因此上面代码第4行打开编译将会出错。
`let s1 = s`执行后就发生了所有权的转移String类型值的所有权从`s`转移到了`s1`。此时Rust认为原来的`s`不再有效。因此上面代码第4行打开编译将会出错。
#### 5. 浅拷贝与深拷贝
## 5. 浅拷贝与深拷贝
- 浅拷贝
只拷贝栈上的内容,就叫做浅拷贝。
对于上面的String类型执行let s1 = s后只把s的ptr、len、cap中的值拷贝给s1的ptr、len、cap的值这种就叫做浅拷贝。浅拷贝发生后s的ptr和s1的ptr都指向同样的堆内存。内存布局如下
对于上面的String类型执行`let s1 = s`后,只把`s``ptr``len``cap`中的值拷贝给`s1``ptr``len``cap`的值,这种就叫做浅拷贝。浅拷贝发生后,`s``ptr``s1``ptr`都指向同样的堆内存。内存布局如下:
![注释](../../assets/8.png)
- 深拷贝
除了拷贝栈上的内容外,还拷贝堆内存中的内容,就叫做深拷贝。
对于上面的String类型执行let s1 = s后除了把s的len、cap中的值拷贝给s1的len、cap外还在堆上重新分配一块内存将s的ptr指向的堆内存的内容拷贝到这块内存然后s1的ptr指向这块内存这种拷贝就叫做深拷贝。深拷贝发生后s的ptr和s1的ptr指向不同的堆内存但是堆内存中存储的内容一样。深拷贝发生后的内存布局如下
对于上面的String类型执行`let s1 = s`后,除了把`s``len``cap`中的值拷贝给`s1``len``cap`还在堆上重新分配一块内存将s的ptr指向的堆内存的内容拷贝到这块内存然后`s1``ptr`指向这块内存,这种拷贝就叫做深拷贝。深拷贝发生后,`s``ptr``s1``ptr`指向不同的堆内存,但是堆内存中存储的内容一样。深拷贝发生后的内存布局如下:
![注释](../../assets/9.png)
显然,**Rust中变量赋值Rust中叫所有权转移使用的是浅拷贝**。
#### 6. Clone
当需要拷贝堆上的数据时可以使用clone方法完成深拷贝的操作如下
## 6. Clone
当需要拷贝堆上的数据时,可以使用`clone`方法,完成深拷贝的操作,如下:
```Rust
fn main() {
let s = "Hello world!".to_string();
@@ -206,21 +225,22 @@ fn main() {
println!("s1: {:?}", s1);
}
```
不过不是所有的类型都能使用clone方法进行深拷贝只有实现了Clone trait的类型才能调用该方法。
不过不是所有的类型都能使用`clone`方法进行深拷贝,只有实现了`Clone trait`的类型才能调用该方法。
#### 7. Copy
按照Rust所有权规则第二条**在任意时刻,值有且仅有一个所有者**。所以当let a = b发生时就将变量b拥有的值移到了a上此时a应该回到未初始状态但实际情况并不一定。不一定的原因是部分类型实现了Copy trait在值移动时会对值进行自动拷贝能让变量a仍拥有原来的值。
## 7. Copy
Rust中,默认实现了Copy trait的类型有:
按照Rust所有权规则第二条,**在任意时刻,值有且仅有一个所有者**。所以当`let a = b`发生时,就将变量`b`拥有的值移到了`a`上,此时`a`应该回到未初始状态,但实际情况并不一定。不一定的原因是,部分类型实现了`Copy trait`在值移动时会对值进行自动拷贝能让变量a仍拥有原来的值。
- 所有整数类型比如u32
- 所有浮点数类型比如f64
- 布尔类型bool它的值是true和false
- 字符类型char
- 元组当且仅当其包含的类型也都是Copy的时候。比如(i32, i32)是Copy的但(i32, String)不是
Rust中默认实现了`Copy trait`的类型有:
- 所有整数类型,比如`u32`
- 所有浮点数类型,比如`f64`
- 布尔类型,`bool`,它的值是`true``false`
- 字符类型,`char`
- 元组,当且仅当其包含的类型也都是`Copy`的时候。比如`(i32, i32)`是Copy的`(i32, String)`不是;
- 共享指针类型或共享引用类型。
#### 8. 所有权和函数
## 8. 所有权和函数
- 将值传给函数
在将值传递给函数时,和变量赋值一样会发生值的移动(或复制),如下:

View File

@@ -1,5 +1,7 @@
### 3.7.2 引用与借用
# 3.7.2 引用与借用
考虑如下代码:
```Rust
fn main() {
let s2 = String::from("hello");
@@ -12,9 +14,11 @@ fn print(s: String) {
println!("s is: {:?}", s);
}
```
第4行打开注释编译将发送错误因为s2的所有权在第3行已经转移到print函数中了s2将不再有效因此第4行不能再使用。
如果要在调用print函数后仍然能使用s2根据本书目前学过的Rust知识则需要将所有权再从函数转移到变量然后使用代码如下
第4行打开注释编译将发送错误因为s2的所有权在第3行已经转移到`print`函数中了s2将不再有效因此第4行不能再使用。
如果要在调用`print`函数后仍然能使用s2根据本书目前学过的Rust知识则需要将所有权再从函数转移到变量然后使用代码如下
```Rust
fn main() {
let s2 = String::from("hello");
@@ -27,10 +31,13 @@ fn print(s: String) -> String {
s //将s的所有权返回
}
```
除了这种转移所有权的方式外Rust还提供了引用的方式可以借用数据的所有权。
#### 1. 引用与借用
## 1. 引用与借用
引用本质上是一个指针,它存储一个地址,通过它可以访问存储在该地址上属于其它变量的数据。与指针不同的是,引用确保指向某个特性类型的有效值。对于一个变量的引用就是在此变量前面加上&符合。
```Rust
let a = 5u32;
let b = &a; // b是对a的引用
@@ -38,7 +45,8 @@ fn print(s: String) -> String {
let c = String::from("hello");
let d = &c; // d 是对c的引用
```
上面代码中变量a、b、c、d的内存布局如下
上面代码中,变量`a``b``c``d`的内存布局如下:
![注释](../../assets/10.png)
@@ -76,6 +84,7 @@ fn print(s: &String) {
```
引用只能使用变量,并不允许改变变量的值,如果需要改变变量,需要使用可变引用(下节内容),下面的代码会报错:
```Rust
fn main() {
let s = String::from("hello");
@@ -86,12 +95,14 @@ fn change(some_string: &String) {
some_string.push_str(", world"); //借用不允许改变变量的值,此行报错
}
```
与引用相对的是解引用,符号为*,本书后续讲解。
#### 2. 可变引用
与引用相对的是解引用,符号为`*`,本书后续讲解。
## 2. 可变引用
- 使用可变引用
可以通过可变引用改变变量的值,对一个变量加上& mut就是对其的可变引用示例如下
可以通过可变引用改变变量的值,对一个变量加上`&mut`就是对其的可变引用,示例如下:
```Rust
fn main() {
let mut s = String::from("hello");
@@ -106,6 +117,7 @@ fn change(some_string: &mut String) {
- 引用的作用域
前文讲过变量的作用域是从定义开始到花括号结束的位置,如:
```Rust
{
...
@@ -115,6 +127,7 @@ fn change(some_string: &mut String) {
```
引用的作用域和变量的作用域有些区别,**在老版本编译器Rust 1.31 之前)中,引用的作用域和变量的作用域一样,也是从定义的位置开始到花括号之前结束**,如下:
```Rust
{
...
@@ -123,7 +136,9 @@ fn change(some_string: &mut String) {
....
}// 花括号之前为r的作用域结束位置
```
**在新版本编译器中,引用作用域的结束位置从花括号变成最后一次使用的位置**,如下:
```Rust
{
...
@@ -136,6 +151,7 @@ fn change(some_string: &mut String) {
- 使用可变引用的限制
1限制一**同一作用域,特定数据只能有一个可变引用**。如下代码会报错:
```Rust
fn main() {
let mut s1 = String::from("hello");
@@ -146,6 +162,7 @@ fn main() {
```
但是下面的代码可以的(新老编译器都可以)
```Rust
fn main() {
let mut s = String::from("hello");
@@ -163,6 +180,7 @@ fn main() {
```
下面的代码在新编译器中也是可以的:
```Rust
fn main() {
let mut s = String::from("hello");
@@ -178,6 +196,7 @@ fn main() {
```
2限制二**同一作用域,可变引用和不可变引用不能同时存在**。如下代码编译错误:
```Rust
fn main() {
let mut s = String::from("hello");
@@ -188,7 +207,9 @@ fn main() {
println!("{}", r3);
}
```
下面的代码在新编译器中是可以的:
```Rust
fn main() {
let mut s = String::from("hello");
@@ -213,10 +234,11 @@ fn main() {
数据竞争会导致未定义行为难以在运行时追踪并且难以诊断和修复Rust 避免了此情况的发生,因为它甚至不会编译存在数据竞争的代码!
#### 3. 悬垂引用
## 3. 悬垂引用
- 什么是悬垂引用(悬垂指针)?
在具有指针的语言中如C/C++很容易通过释放内存但是保留指向它的指针而错误的生成一个悬垂指针。例如有如下C代码
```C
#include <stdio.h>
#include<stdlib.h>
@@ -238,6 +260,7 @@ int main()
return 0;
}
```
在执行第14行前其内存布局为
![注释](../../assets/11.png)
@@ -246,11 +269,12 @@ int main()
![注释](../../assets/12.png)
第14行执行后ptr就变成了一个悬垂指针或者交悬垂引用然后在第16行继续使用ptr则会发生错误。
第14行执行后`ptr`就变成了一个悬垂指针或者交悬垂引用然后在第16行继续使用`ptr`,则会发生错误。
- 在 Rust 中,编译器确保引用永远不会变成悬垂状态。
如下代码因为会产生悬垂引用,编译将不会通过:
```Rust
fn main() {
let reference_to_nothing = dangle();
@@ -274,7 +298,8 @@ fn no_dangle() -> String {
} // 此处s虽然离开了函数这个作用域范围但是它的所有权是被转移出去了值并没有释放
```
#### 4. 引用的规则总结
## 4. 引用的规则总结
引用的规则可以总结如下:
- 在任意给定时间,要么 只能有一个可变引用,要么 只能有多个不可变引用。

View File

@@ -1,16 +1,18 @@
### 3.7.3 Slice类型
# 3.7.3 Slice类型
Slice切片类型表示引用集合中一段连续的元素序列。Slice是一类引用没有所有权。Rust常见类型中有三种支持Slice的操作分别是String、数组、Vec类型。
#### 1. Slice类型
## 1. Slice类型
假定s是可被切片的数据则对应的操作有
- s[n1..n2]获取s中index=n1到index=n2(不包括n2)之间的所有元素;
- s[n1..]获取s中index=n1到最后一个元素之间的所有元素
- s[..n2]获取s中第一个元素到index=n2(不包括n2)之间的所有元素;
- s[..]获取s中所有元素
- 其他表示包含范围的方式如s[n1..=n2]表示取index=n1到index=n2(包括n2)之间的所有元素。
- `s[n1..n2]`获取s中index=n1到index=n2(不包括n2)之间的所有元素;
- `s[n1..]`获取s中index=n1到最后一个元素之间的所有元素
- `s[..n2]`获取s中第一个元素到index=n2(不包括n2)之间的所有元素;
- `s[..]`获取s中所有元素
- 其他表示包含范围的方式,如`s[n1..=n2]`表示取index=n1到index=n2(包括n2)之间的所有元素。
Rust中几乎总是使用切片数据的引用。切片数据的引用对应的数据类型描述为`&[T]``&mut [T]`前者不可通过Slice引用来修改源数据后者可修改源数据。示例如下
Rust中几乎总是使用切片数据的引用。切片数据的引用对应的数据类型描述为&[T]或&mut [T]前者不可通过Slice引用来修改源数据后者可修改源数据。示例如下
```Rust
fn main(){
let mut arr = [11,22,33,44];
@@ -30,8 +32,10 @@ Slice类型是一个胖指针包含两个字段
- 第一个字段是指向源数据中切片起点元素的指针;
- 第二个字段是切片数据中包含的元素数量,即切片的长度。
#### 2. String的切片类型
String的切片类型为&str而不是&String其使用方式如下
## 2. `String`的切片类型
`String`的切片类型为`&str`而不是`&String`,其使用方式如下:
```Rust
fn main() {
let s = String::from("hello world!");
@@ -42,12 +46,14 @@ fn main() {
}
```
&str&String的内存布局如下
`&str``&String`的内存布局如下:
![注释](../../assets/13.png)
#### 3. 其它Slice
## 3. 其它Slice
数组的Slice如下
```Rust
fn main() {
let a: [u32; 5] = [1, 2, 3, 4, 5];
@@ -55,7 +61,9 @@ fn main() {
println!("b: {:?}", b);
}
```
Vec的Slice如下
```Rust
fn main() {
let v: Vec<u32> = vec![1, 2, 3, 4, 5];