rust学习
rust
安装
linux
执行命令:
1 |
|
windows
下载安装包rustup-init.exe
执行安装时一路回车就行
检查安装
执行命令:
1 |
|
编译单个源文件:
1 |
|
新建一个项目:
1 |
|
编译项目:
1 |
|
编译并运行:
1 |
|
检查代码是否可以通过编译(但没有编译结果), 运行速度比cargo build
快得多
1 |
|
查看rust文档
1 |
|
基础
变量和可变性
变量
rust使用let
来声明一个变量, 默认情况下是不可变
的
1 |
|
在使用let
的同时使用mut
(mutable, 可变的)来标记变量可变
1 |
|
一般情况下rust会自动根据上下文推断变量类型, 如上面的x就是
i32
类型指定变量类型时需要这样写:
1
let x : i32 = 5;
常量
rust使用const
来声明一个常量, 在任何情况下常量都不可变, 常量的命名全大写
1 |
|
声明常量时必须同时指定常量的类型和值, 因为在此之后常量的类型和值永不可变, 并在其作用域中永远可用
遮蔽
在使用let
声明一个不可变变量后, 再次使用let
声明一个同名的新变量, 原来的变量类型和数据将会被新的变量的类型和数据覆盖
1 |
|
遮蔽实现了变量的重新使用, 在上面的例子中, 不需要为两个变量分别命名为
str
和str_len
而是直接将原来的变量给遮蔽
数据类型
rust是静态类型语言, 即在编译期间, rust必须要知道所有变量的确切数据类型, 当数据的类型可能是多种情况时就必须指定变量的类型
1 |
|
在上面的代码中, 定义了一个str1
变量, 没有特定指定类型, 因为rust会自动根据上下文将其解析成 &str
类型
第二行使用&str
的.trim()
方法将字符串中的空白字符去除, 返回的仍是&str
接着使用了parse()
将数据进行解析, 然而parse()
并不知道需要解析成什么类型的数据, 因此在第二行遮蔽str1
时指定了数据类型为u32
, 则parse()
将会将其解析成u32
, 否则报错
parse()
的返回类型为Result
, 是rust中的一种重要数据类型枚举
, 其有两个变体
- Ok(data): 当数据解析成功后会返回解析后的数据
- Err: 当数据解析失败后会返回Err这个变体, 其内置了一些方法, 上面用到的
expect()
就是在发生错误时退出程序并将其括号内的内容打印出来
rust中的标量类型就是单个值的类型, 四个基本类型为整型
, 浮点型
, 布尔型
, 字符
整型
rust中的整形型只有长度和有无符号之分:
长度 | 有符号 | 无符号 |
---|---|---|
8 | i8 | u8 |
16 | i16 | u16 |
32 | i32 | u32 |
64 | i64 | u64 |
128 | i128 | u128 |
arch | isize | usize |
isize
和usize
的长度取决于arch
也就是当前使用的计算机的架构64位系统则
isize
就是i64
, 32位系统isize
就是i32
,usize
同理
在任意整型字面量中可以使用_
来进行分隔, 提高数据的可读性如:
数字字面量 | 示例 |
---|---|
十进制 | 100_000 |
十六进制 | 0x50_4b |
八进制 | 0o17_60 |
二进制 | 0b1010_1100 |
字节(即u8 ) |
b’A’ |
可能属于多种类型的数字字面量可以使用后缀来指定数据类型, 如
57u8
和2222u16
等当然一般情况下可以不指定整型的具体类型, rust会自动把整型解析成
i32
整型溢出
当一个值的大小超过这个某个变量的类型所能承载的最大值时会发生整型溢出
调试时rust会指出错误并发生恐慌, 而在发布时rust并不会发生恐慌而是进行环绕
如变量byte1
的类型为u8
, 可以承载的数据大小是0-255
, 如果给btye1
赋值为256
时数据会进行环绕, 相当于对256
取模
最终256
会变成0
, 257
会变成1
浮点型
rust中的浮点型有两种, 单精度f32
和双精度f64
, rust的默认浮点类型为f64
所有的浮点型都有符号
数字运算
rust的数字类型与其他语言一样支持加减乘除和取模操作
1 |
|
布尔类型
rust中的布尔类型占用一个字节长度, 分为true
和false
, 声明时使用bool
[!note]
需要注意的是, 不同于python, php等语言, rust永远不会尝试将
非布尔类型
的数据转换为布尔类型
下面的代码在python中成立:
1
2
3
a = 5
if a:
print("if判断为真")但在rust中同样功能的代码则会直接报错:
1
2
3
4
let a = 5;
if a {
println!("if判断为真")
}因为
if
期望的是一个严格的bool
类型, 并且不会像python一样自动把非空的值转换为true
字符类型
rust的字符类型使用char
声明, 支持所有的unicode字符, 使用单引号包裹, 占用四个字节长度
1 |
|
元组类型
rust使用元组类型将多种类型的多个值组合在一起, 长度在声明时就固定, 之后不可改变
1 |
|
可以通过模式匹配来对元组中的数据进行解构:
1 |
|
也可以使用.
接元素索引来直接访问元组的元素:
1 |
|
有一类元组类型很特殊, 即空元组
()
, 被称为单元类型
, 如果一个表达式或函数不返回任何值, 则隐式的返回单元值
数组类型
数组类型跟元组类似, 长度同样不可变, 但其中的元素类型必须相同
1 |
|
需要指定类型时需要同时指定数组内元素的类型和个数:
1 |
|
如果需要创建一个包含同一个元素的数组可以这样创建:
1 |
|
与元组不同, 数组访问元素是使用[]
1 |
|
数组越界
在其他语言中常常会发生数组越界的情况, 即访问的索引大于数组的最大索引
1 |
|
这段代码在编译时就会被rust发现并报错
如果代码中索引值在运行时才能确定, 则编译会通过, 但运行时仍会被rust发现, 终止程序并报错
1 |
|
这里代码中的索引值需要等待运行时的用户输入, 因此编译可以通过
函数
rust使用fn
定义一个函数:
1 |
|
rust中的函数定义可以在main
函数之后, 只要定义的函数在作用域中, 就始终可用
参数
rust函数同样可以被定义为有参数, 向函数传入参数时同样需要指定参数的数据类型:
1 |
|
这里在定义时使用的临时占位参数x
被称为形参
, 即形式上的参数
, 而在调用时传入的参数50
被称为实参
, 即实际传入的参数
同样的, 函数可以有多个参数, 每个参数之间使用,
隔开:
1 |
|
语句和表达式
rust函数由一系列语句和表达式组成, 语句和表达式的区别如下:
语句
是执行一些操作而不返回值的指令表达式
会计算并产生一个值
1 |
|
表达式与语句不同, 在表达式结束时, 结尾没有
;
, 如果在表达式末尾加上;
则会将其转化为语句
带有返回值的函数
函数可以向调用此函数的代码返回值
返回值是匿名的, 不需要对其进行命名, 但需要在函数定义时预先声明它的数据类型:
1 |
|
在上面的代码中, 10
是函数return_ten
的唯一表达式, 也是最后的表达式
它的计算结果也就是10
, 将会被作为返回值返回给调用它的代码也就是five()
再交由let
语句, 将其值绑定到变量x
, 最后打印出来
函数体的最后一个表达式的计算结果会默认作为函数的返回值返回(如果函数有返回值的话)
上面的代码中:
1
2
3
fn return_ten() -> i32 {
10
}等价于:
1
2
3
fn retrun_ten() -> i32 {
return 10;
}但一般情况下会省略
return
, 如果想要提前返回, 写return
也是可以的
注释
rust中使用//
及其变体来代表注释
单行注释:
1 |
|
多行注释:
1 |
|
跨行注释:
1 |
|
控制流
控制语句与其他语言类似, 同样的, rust中也有条件语句
, 循环控制语句
等, 只不过实现方式稍有差别
if表达式
rust中if
的使用方法与其他语言类似:
1 |
|
同样的, rust中也有else if
:
1 |
|
在
布尔类型
提到过, if期待的是严格的bool
类型而不能python等语言中的if a
来用一个非空值代表布尔值true
不光在if中, 整个rust中, 任何需要用到
bool
类型的地方, 其输入也一定是严格的bool类型
在let中使用if
由于if
是一个表达式, 所以可以使用let
将if
的计算结果赋值给变量:
1 |
|
当a为true
时, if
表达式的计算结果为5
, 最后number
被赋予5
, 另外的情况则是6
需要注意的是, 在
let
中使用if
表达式时, 不同分支最后的返回值类型也一定需要相同下面的代码是不可以的:
1
2
3
let a = true;
let result = if a { 5 } else { "six" };因为rust在编译时需要知道
result
的确切数据类型, 而这里有两种可能:整型
和字符串切片
循环
rust提供了三种方法来实现代码的循环执行, loop
, while
和for
loop循环
rust中的loop
相当一个死循环, loop
中的语句会无限次执行直到触发某个条件明确要求停止
1 |
|
上面的代码中没有指明何时停止循环, 所以只有在运行时手动输入
ctrl+c
, 代码才会停止
和其他语言一样, rust使用break
来跳出上一级循环, continue
来跳出当次循环
1 |
|
这里指明了跳出循环的条件, 即
count == 10
, 当count
每次加1, 最终等于10
时循环被打破
1 |
|
这段代码读取了用户输入并使用
if
来判断用户输入是否为quit
, 当输入quit
跳出循环并退出程序输入其他内容则跳过当次循环, 继续进行下一次循环
循环标签
在正常情况下, break
和continue
跳出或跳过的循环都是最近的循环
rust提供了循环标签来实现内层循环的break
和continue
跳出外层特定循环
循环标签的写法如下:
1 |
|
在循环前加上'标签名:
就能为循环打上标签, 在内层循环时只要使用break '标签名;
就能打破指定标签的循环
上面的代码在
loop2
中使用了break 'loop1;
跳出了loop1
循环
continue
同理
从循环返回
loop
循环同样可以用于返回值:
1 |
|
在停止循环的break
表达式后添加想要返回的值, 这个值将会作为循环的返回值返回
while循环
rust提供while
循环以便于在每次循环之前执行一些检查以确定是否要继续循环:
1 |
|
for循环
可以使用while
循环来遍历访问数组等类型中的所有元素:
1 |
|
这样使用有一个坏处, 那就是很容易写错index
的范围导致数组越界或者没能完全遍历所有元素
rust提供更为简洁的方案for
循环来对一个集合的每一个元素执行一些操作
写法类似python中的for i in
1 |
|
同样的, rust中的for
也有类似python中的for i in range()
写法:
1 |
|
所有权
所有权
是rust中最为与众不同的特性, 正是它让rust不需要垃圾回收器(GC)也能保证内存安全, 这部分是rust的重要一环
前置: 堆和栈
在很多语言中编程时并不需要考虑数据实在堆还是栈上, 但在rust中, 一个数据在堆上还是在栈上的影响极为重要
栈(stack)
和堆(heap)
都是程序运行时可以使用的内存, 区别在于它们的结构:
栈
遵循先进后出规则, 就像叠起来的砖, 后放上去的需要先拿走, 所有的数据必须占用已知并且的大小堆
则用来存储在编译时未知大小的数据, 需要在运行时向系统申请才能正常使用
一般情况下, 访问堆
上的数据总是比访问栈
上的数据要慢
在代码的运行过程中需要实时追踪堆上分配的无用内存是否被清理, 否则会占用大量内存并拖慢系统和程序运行, 这正是所有权需要解决的问题
所有权的规则
所有权的规则可以概括为三点:
- rust中每一个值都有一个被称为它的
所有者
的变量 - 一个值在任意时刻下有且只有一个所有者
- 当所有者离开作用域时, 这个值就会被丢弃
作用域
下面是一个作用域的例子:
1 |
|
String类型
下面使用String
类型来举例说明所有权的规则
创建一个String
类型可以使用String
下的new
或者from
函数, 区别在于new
创建一个空实例, from
基于一个字面量:
1 |
|
同时, String
类型还是可以修改的:
1 |
|
为什么要单独说
String
是可变的:在rust中, 普通的字符串都是指向栈上字符串的一个不可变引用
&str
:
1
let a = "hello world";
这里的
a
是一个始终不可变的引用即使使用
let mut
;
1
2
3
let mut a = "hello world";
a = "你好";只是可以把a绑定到另一个
&str
类型的你好
上,hello world
这个字符串仍然没有改变
内存与分配
像上面说的:
1 |
|
由于hello world
这个字符串字面量在编译时就确定了内容和长度, 所以这段数据会被硬编码进最后的二进制可执行文件的可读数据段中, 这使得程序在读取和使用这些数据时的速度很快
但对于一些大小未知的文本, rust不可能将一块内存放进二进制文件中, 更何况它的大小还可能随着程序运行变化
对于rsut的String
类型, 为了支持一个可变, 可增长的文本片段, 需要在堆上分配一个在编译时是位置大小的内存来存放数据, 而为了实现String
的这些特性, 需要:
- 在运行时向内存分配器请求一块内存
- 处理完后需要将内存返回给分配器
rust中第一步由String::from()
完成, 不同语言的不同之处在于第二步:在有垃圾回收机制的语言如python中, 垃圾回收器会记录并回收不使用的内存;在没有垃圾回收机制的语言中则需要显式地进行内存回收
rust采用的方法则是: 内存在拥有它的变量离开作用域后就被自动释放
下面是一个作用域例子的String
版本:
1 |
|
数据与变量的交互(移动)
使用整型进行数据移动:
1 |
|
这里将数据
5
绑定到变量a
, 然后将a
绑定到b
, 实际是在栈上复制了一块相同的内存此时栈上有两个
5
, 原因是栈上的数据相对简单且固定, 所以复制起来快速且高效
使用String
写同样的代码:
1 |
|
这里的代码则会报错, 因为数据
hello
实际在堆中, 而a
和b
只是栈上的引用, 尝试将a
绑定到b
时, 实际上是将内存中的hello
的所有权进行移交, 数据的拥有者从a
变成了b
, 因此a
不再可用为什么不像整型可以直接复制,
String
只能转移所有权:前面说过,
a
实际上存放在栈上, 它是一个引用, 指向堆上的数据hello
,把
a
绑定到b
上时, 如果直接复制一个栈上的引用, 那么数据hello
就会有两个引用, 也就是两个拥有者它们离开各自的作用域时都会导致内存中的
hello
被释放, 则另一个引用就会指向一块被释放的内存, 也就是悬垂引用, 这是不允许的而当两个引用都离开各自的作用域时, 同一块内存就会进行两次内存释放(每个引用离开时释放一次), 也就是双重释放, 这也是不允许的
使用rust中的所有权机制来解决这个问题就是移交
a
的所有权至b
, 同时a
不再可用
数据与变量的交互(克隆)
如果确实需要将内存中的数据复制一份, rust也提供了clone()
方法:
1 |
|
这里使用了
clone
方法对a
及其指向的内存进行了克隆, 此时a
和b
指向了不同的内存空间, 两块内存空间中存储形同的数据, 所有引用a
和引用b
都是可用的
只在栈上的数据(复制)
对于直接存放在栈上的数据, 像上面说的, 会直接进行复制, 因为这种复制是快速高效的:
1 |
|
可以直接像这样进行复制的数据类型还有:
- 所有整数类型, i32, u32等
- 布尔类型, true和flase
- 所有浮点类型, f32和f64
- 字符类型, char
- 元组, 当其包含的元素都可以复制时, 元组也可以复制, (i32, char, f64)可以, (i32, char, String)不行
所有权和函数
向函数传递一个值与给变量赋值类似, 向函数传递的值也可能会被移动或者复制:
1 |
|
返回值与作用域
返回值同样可以转移所有权, 在函数的最后, 数据如果作为返回值返回, 则所有权被移交至返回值:
1 |
|
变量的所有权总是遵循同样的模式: 将值赋给另一个变量时移动它
可以使用元组返回多个值, 并使用模式匹配来解构并使用:
1 |
|
上面的代码中, s1进入函数后, 计算了长度, 并返回了长度和其本身
但是这似乎确实有点形式主义了, 为什么s1本身一定要传入再传出呢
引用和借用
rust提供了引用来实现函数对值的使用而不获取其所有权:
1 |
|
创建一个引用的行为就叫做借用
而根据rust的默认不可变原则, 创建的引用也是不可变的, 下面尝试修改借用到的值:
1 |
|
实际上上面的代码会报错, 因为引用默认不可变, 也就是无法通过修改一个不可变引用来改变其指向的值
可变引用
和普通标量一样, 引用也有可以有可变引用:
1 |
|
可变引用允许通过修改引用的值来修改对应指向的数据, 前提是这个变量也是可变的mut
引用的规则:
- 在同一作用域内, 一个变量可以同时有多个不可变引用
- 在同一作用域内, 一个变量不能同时有不可变引用和可变引用
- 在同一作用域内, 一个变量最多只能有一个可变引用
上面这些规则的目的是为了限制数据竞争
, 数据竞争在以下三个条件同时发生时发生:
- 两个以上的指针访问同一数据
- 至少有一个指针用于写入数据
- 系统没有同步数据访问的机制
rust可以使用
{}
来划定作用域, 来允许数据拥有多个可变引用, 但不是同时拥有
1
2
3
4
5
6
7
8
9
fn main() {
let mut s = String::from("hello");
{
let r1 = &mut s;
} // r1 在这里离开了作用域,所以我们完全可以创建一个新的引用
let r2 = &mut s;
}
悬垂引用(悬垂指针)
在具有指针的应用中可能会发生指针存在, 而其指向的数据已经被错误的释放的情况, 这种情况下的指针就叫悬垂指针
, 在rust中则一般不会发生这种问题(因为所有权的存在)
1 |
|
上面的代码中, s离开函数后将会被回收, 而其引用将会被函数作为返回值返回给main
函数中的n
也就是说, 在s
离开函数, 被回收后, 其引用&s
仍然存在, 在某些语言中这回导致很多问题, 如&s
对应内存的二次释放, 通过&s
对已经释放过的内存进行操作等, 但在rust中, 编译器会直接报错
切片
另一个没有所有权的数据类型是slice
, 也就是切片
, 切片允许你使用集合中一段连续的元素而不引用整个集合
尝试获取字符串中的第一个单词(获取第一个空格的索引):
1 |
|
上面的(i, &item)
代表每个元素的索引i
和其内容的引用&item
组成的元组
想要找到空格对应的索引, 只需判断&item
是否等于空格, 若是则返回对应的i
在上面的代码中, 找到的
i
是一个和字符串string
无关的一个值, 因为这个值是通过函数计算出来的, 而并未与string
绑定, 也就是说在string
被清理后仍然可用, 但不一定有意义
1
2
3
4
5
6
7
8
9
fn main() {
let string = String::from("hello world");
let word_index = frist_word(string); // 这里计算出word_index的值为5
string.clear(); // clear方法清空字符串
// word_index的值仍然可用并仍然是5, 但string已经变化
}
如果要按照上面的代码进行查找, 那么第二个单词对应函数应该是这样的:
1 |
|
这些值都与原来的字符串没有关系, 只是对应的数值, rust引入了slice来解决这个问题
字符串切片
字符串切片是对String
中的一部分值的引用, 看起来长这样:
1 |
|
很容易看出切片的写法&引用字符串变量[开始返回..结束范围]
, 开始和结束范围左闭右开
和python等语言类似, 范围的默认值是[字符串开头..字符串结尾]
, 所以当索引为开头或结尾时可以省略
有了字符串切片, 上面的代码就能进一步改进:
1 |
|
和上面获取的索引不同, 字符串切片是对源数据的一部分的不可变引用, 因此源数据不可能再拥有一个可变引用
也就是说, 源数据不再可修改:
1
2
3
4
5
6
7
fn main() {
let string = String::from("hello world");
let word = frist_word(string); // 这里获取了string的一个不可变引用
string.clear(); // 这里将会发生错误, 因为需要clear字符串需要一个可变引用来操作
}
字符串字面量就是slice
在String类型中提到过, rust中的字符串字面量其实就是指向二进制程序中特定位置的切片, 其类型是&str
&str
绝对不可变, 因为其数据在编译时就静态写入了二进制文件中, 进入内存时对应的内存也不可变
字符串slice作为参数
在知道了前面的内容后, 代码可以写成现在的形式:
1 |
|
其他类型的slice
与字符串类似, 数组类型也有切片:
1 |
|
可以像字符串切片一样使用数组切片
结构体
结构体struct
是rust中的一个重要类型, 它允许命名和包装多个相关的值组成一个有意义的集合
定义和实例化结构体
rust使用struct
定义一个结构体:
1 |
|
在大括号里写上数据名:数据类型
来定义结构的的字段, 每个字段用,
分隔
使用上面的定义实例化一个用户:
1 |
|
可以通过结构体更新语法使用更少的代码达到更新结构体实例的效果:
1 |
|
上面的更新语句就像带有
=
的赋值语句除了user2中的
username
是更新的, user1中的所以user最终可用的部分是
user1.username
和user1.age
访问结构体数据
和元组类似, 访问结构体数据可以使用.
:
1 |
|
没有命名字段的结构体
rust还提供与元组类似的结构体, 结构体中只有类型而没有命名的就叫元组结构体
, 通常用于给元组取一个名字:
1 |
|
在上面的代码中
black
和orign
并不是同一个类型, 而是Color
和Point
类型, 只是在类别上属于结构体
没有任何字段的结构体
没有任何字段的结构体被称为类单元结构体
, 它们类似于单元类型, 在结构体中的用途一般是为了实现一些方法而不存储数据
使用结构体的示例程序
计算长方形面积
编写一个代码尝试计算一个长方形的面积, 并逐步使用结构体来替代简单变量:
1 |
|
由于长方形长和宽是关联的, 上面的代码单独定义了两个变量, 传入size
函数的也是两个, 这两者之间并无关联
使用元组重构
下面使用元组重构:
1 |
|
问题又来了, 这里的s.0
和s.1
在代码中很难体现长方体的性质, 在需要进行其他操作如绘图时很难体现宽和高的区别
使用结构体重构
下面使用结构体来重构代码:
1 |
|
尝试打印实例
尝试使用println!
打印rect1
的值时, rust会报错:
1 |
|
因为自定义的结构体并没有实现Display
, rust不知道该以什么格式将数据打印出来
rust提供了debug
来输出调试信息所以代码可以改成下面的模式:
1 |
|
代码仍然会报错, 因为Rectangle
这个结构体并没有实现Debug
方法, 需要显示的引入外部属性:
1 |
|
现在可以成功打印出rect1
的类型和值:
1 |
|
将:?
改成:#?
可以增加打印结果的可读性:
1 |
|
方法
方法
和函数
类似, 同样使用fn
定义, 一样可以传入参数和传出返回值
它们的第一个参数总是self
, 意为包含这个方法的结构体或枚举类型实例本身
定义方法
使用上面Rectangle
的定义, 为其定义一个size
方法, 为结构体定义方法使用impl
关键字:
1 |
|
有多个参数的方法
实现一个判断长方体能否被另一个长方体完全覆盖的方法:
1 |
|
关联函数
在impl
块中定义的函数叫关联函数
, 其参数的第一个不是&self
, 下面创建一个正方形关联函数:
1 |
|
多个impl块
同一个结构体的impl
块可能有多个
将方法和关联函数分散写在多个impl
块中或全写在一起在语法上都是可以的
枚举
枚举是rust中另一个重要的数据类型,允许通过列举可能的成员来定义一个类型
定义枚举
假设需要定义一个ip地址, 它要么是ipv4要么是ipv6, 而不能两者都是, 这时可以使用枚举类型来定义:
1 |
|
在上面的代码中, 定义了一个IpKind
枚举类型, v4
和v6
是它的成员
, 成员的命名方式是单词开头为大写
在类型上看, v4
和v6
是同一个类型, 只是不同的变体
枚举值
创建枚举类型的两个不同实例:
1 |
|
这里的
ip4
和ip6
是同一个类型IpKind
, 但是其的不同变体
结合结构体创建新的类型:
1 |
|
但是在上面的代码中同时使用了两种数据类型结合才实现了一个数据的功能
枚举类型实际上可以直接关联对应的数据类型:
1 |
|
同一个枚举类型的不同变体的数据类型可以不同:
1 |
|
枚举与空值
在rust中并没有其他语言中的空值, 即所谓的null
, 取而代之的是一个枚举类型option<T>
在其他语言中尝试像访问正常值一样使用一个空值将会引起一些不可预知的错误, 在rust中并没有null
这样的东西, 但这样的概念存在于预导入模块的option<T>
枚举变量中, 这里的<T>
代表泛型, 意思是有多个数据类型都能使用option
枚举
1 |
|
上面可以看到option
枚举的变体只有两个, Some()
和None
这样使用None
的优点在于, 在不使用option
枚举时, 代码中的值不可能为None
, 只有当使用一个option
枚举类型的值时这个值才有变成None
的可能, 缩小了可能出现None
也就是null
的范围
1 |
|
match控制流
rust中match
来进行分支运算, 对一个值进行匹配, 通过不同的匹配结果来进行不同的操作:
1 |
|
上面的代码尝试对coin
的具体变体进行匹配, 根据不同的变体类型返回不同的值或操作, =>
的后面可以直接返回值也可以进行操作:
1 |
|
匹配Option<T>
前面提到使用option<T>
的初衷是防止null
也就是None
的泛滥, 当option<T>
为Some<T>
时我们需要对其中的值进行提取, 出现None
则不做操作, 这一步骤同样可以使用match
来实现:
1 |
|
匹配一定穷尽
使用match
进行匹配值, 需要列出匹配对象所有的可能性, 如:
1 |
|
上面的代码是行不通的, 在进行匹配中, x的所有可能没有被列出来, 当x为None
时, 编译器不知道该如何处理这个值或如何进行操作, 因此在写match
就应当考虑所有的可能性并全部列出
通配符和_
占位符
当需要完全覆盖所有可能, 但没有办法手动列出时, 可以使用占位符来替代某些特定的可能
1 |
|