Rust 从 0 到 1 (流程控制,匹配模式,方法)
Rust 从 0 到 1 (流程控制,匹配模式,方法)
流程控制
在所有的编程语言都有 if else 流程控制语句,如果没有请告诉我!
if else
Rust 中的 if else 语法如下:
1 | let condition = true; |
读作:若 condition 的值为 true,则执行 A 代码,否则执行 B 代码。
在 Rust 中 if else 还有一种用法是:
1 | let condition = true; |
这里的代码解释为:如果 condition 为真,那么 number 的值为 5 否做为 6。这种用法需要注意的是:返回类型要一致,如果条件不一致就不能通过编译。
else if
if else 可以和 else if 组合实现复杂的条件分支判断,如:
1 | let n = 6; |
有一点要注意,就算有多个分支能匹配,也只有第一个匹配的分支会被执行!
循环控制
在 Rust 语言中有三种循环方式:for、while 和 loop,其中 for 循环比较重要。
for 循环
Rust 中的 for 循环的书写格式:
1 | for 元素 in 集合 { |
for 循环使用的是集合的引用,集合的所有权会被转移(move) 到 for 语句块中,后续就没有办法在使用该集合了,但是对于实现了 copy 特质的数组,则不存在所有权转移的问题。
我们也可以使用 mut 关键字修改元素:
1 | for item in &mut collection { |
for 循环的具体使用方式总结如下:
使用方法 | 等价使用方式 | 所有权 |
---|---|---|
for item in collection | for item in IntoIterator::into_iter(collection) | 转移所有权 |
for item in &collection | for item in collection.iter() | 不可变借用 |
for item in &mut collection | for item in collection.iter_mut() | 可变借用 |
如果你只想单独的循环执行某些事情,用不到元素可以使用如下方法:
1 | for _ in 0..10 { |
在 Rust 中 _ 的含义是忽略该值或者类型的意思
continue
使用 continue 可以跳过当前当次的循环,开始下次的循环,如:
1 | for i in 1..4 { |
break
使用 break 可以直接跳出当前整个循环,如:
1 | for i in 1..4 { |
while 循环
使用一个条件来循环,当该条件为 true 时,继续循环,条件为 false,跳出循环,如:
1 | let mut n = 0; |
当 n 小于等于 5 时,会执行 n = n + 1
,当 n 大于 5 时,则会跳出循环。
loop 循环
loop 是一个简单的无限循环,需要在内部设置条件,当条件满足时就跳出循环,如果内部逻辑出现错误,你将得到一个死循环。如:
1 | loop { |
正确的用法是,loop 与 break 配合使用,如
1 | let mut counter = 0; |
解释:当 counter 递增到 10 时,就会通过 break 返回一个 counter * 2
的值,最后赋值给 reuslt 并打印出来。
这里需要注意的是:
- break 可以单独使用,也可以带一个返回值,有些类似 return
- loop 是一个表达式,因此可以返回一个值
匹配模式
match 匹配
在 Rust 中,匹配模式最常用的就是 match 和 if let,首先 match 的通用形式:
1 | match target { |
看来一个 match 的例子,对于经常挖 src 的小伙伴而言,我们在提交漏洞后审核会给我们的漏洞评级,那么来看下面这个例子:
1 | enum Vul { |
这里我们想去匹配 vul 对应的枚举类型,因此使用三个 match 分支来覆盖漏洞的等级,需要注意的是:
- match 的匹配必须要穷举出所有可能,因此这里用 _ 来代表未列出的所有可能性
- match 的每一个分支都必须是一个表达式,且所有分支的表达式最终返回值的类型必须相同
- X | Y,类似逻辑运算符 或,代表该分支可以匹配 X 也可以匹配 Y,只要满足一个即可
match 跟其他语言中的 switch 非常像,_ 类似于 switch 中的 default。
使用 match 赋值
mathc 本身是一个表达式,因此它可以用来赋值:
1 | enum IpAddr { |
模式绑定
模式匹配的另外一个重要功能是从模式中取出绑定的值,如:
1 | enum Action { |
上面的代码中,在匹配 Action::Say(String)
模式时,我们把内部的存储的值绑定到了 s 变量上,因此 s 变量对应的就是 String 类型。
穷尽匹配
match 的匹配必须穷尽所有情况,如:
1 | enum Vul { |
在这里我们没有处理 Xss 的情况,因此在编译时就会报错:
_ 通配符
当我们不想在匹配时列出所有值的时候,可以使用 Rust 提供的一个特殊模式,我们可以使用 _
代替未列出的值,如:
1 | let some_u8_value = 0u8; |
将 _
放在最后是为让 _
匹配所有遗漏的值。当然你不想用 _
通配符也是可以的,可以使用变量来接收,如:
1 | let some_u8_value = 0u8; |
if let 匹配
在某些场景下只关心一个值是否存在,如果用 match 就要写成这样子:
1 | let v = Some(3u8); |
比如只想对 Some(3)
模式进行匹配,不想处理其他的值,由于 match 表达式穷尽性的要求,需要加上 _ = ()
,这样会增加一些无用的代码。好在 Rust 为了我们提供了简便的方法 if let
:
1 | if let Some(3) = v { |
那么如何选择:当你只要匹配一个条件,且忽略其他条件时就用 if let ,否则都用 match。
matches!宏
Rust 标准库中提供了一个非常实用的宏:matches!,它可以将一个表达式跟模式进行匹配,然后返回匹配的结果 true or false。具体使用方式如下:
1 | enum MyEnum { |
现在需要对 v 进行过滤,只保留 MyEnum::Foo
的元素,使用 matches!
的写法:
1 | v.iter().filter(|x| matches!(x, MyEnum::Foo)); |
变量遮蔽
无论是 match 还是 if let,这里都是一个新的代码块,而且这里的绑定相当于新变量,如果你使用同名变量,会发生变量遮蔽,因此我们需要注意。
解构 Option
在 Rust 中使用 Option 枚举来解决变量中是否有值的问题,Option 枚举的定义如下:
1 | enum Option<T> { |
简单解释就是:一个变量要么有值:Some(T), 要么为空:None。
匹配 Option
首先来看代码:
1 | fn plus_one(x: Option<i32>) -> Option<i32> { |
plus_one 接受一个 Option
模式使用场景
模式是 Rust 中的特殊语法,它用来匹配类型中的结构和数据,它往往和 match 表达式联用,以实现强大的模式匹配能力。模式一般由以下内容组合而成:
- 字面值
- 解构的数组、枚举、结构体或者元组
- 变量
- 通配符
- 占位符
所有可能用到模式的地方
- match 分支
- if let 分支
- while let 条件循环
- for 循环
- let 语句
- 函数参数
- let 和 if let
- …
完整的列表可以查看 全模式列表
方法 Method
Rust 使用 impl 来定义方法:
1 | impl 结构体{ |
例如现在需要计算一个矩形的面积,则有一下代码:
1 | #[derive(Debug)] |
首先定义了一个 Rectangle 结构体,并且在其上定义了一个 area 方法,用于计算该矩形的面积。
impl Rectangle {} 表示为 Rectangle 实现方法(impl 是实现 implementation 的缩写),这样的写法表明 impl 语句块中的一切都是跟 Rectangle 相关联的。
self、&self 和 &mut self
在 area 的签名中,我们使用 &self 替代 rectangle: &Rectangle,&self 其实是 self: &Self 的简写(注意大小写)。在一个 impl 块内,Self 指代被实现方法的结构体类型,self 指代此类型的实例,换句话说,self 指代的是 Rectangle 结构体实例,这样的写法会让我们的代码简洁很多。需要注意的是, self 依然有所有权的概念:
- self 表示 Rectangle 的所有权转移到该方法中,这种形式用的较少
- &self 表示该方法对 Rectangle 的不可变借用
- &mut self 表示可变借用
self 的使用就跟函数参数一样,要严格遵守 Rust 的所有权规则。
使用方法代替函数有以下好处:
- 不用在函数签名中重复书写 self 对应的类型
- 代码的组织性和内聚性更强,对于代码维护和阅读来说,好处巨大
方法名跟结构体字段名相同
在 Rust 中,允许方法名跟结构体的字段名相同,如:
1 | impl Rectangle { |
使用 rect1.width() 时,Rust 知道我们调用的是它的方法,如果使用 rect1.width,则是访问它的字段。
带有多个参数的方法
方法和函数一样,可以使用多个参数,如:
1 | impl Rectangle { |
关联函数
定义在 impl 中且没有 self 的函数被称之为关联函数: 因为它没有 self,不能用 f.read() 的形式调用,因此它是一个函数而不是方法,它又在 impl 中,与结构体紧密关联,因此称为关联函数。
Rust 中有一个约定俗成的规则,使用 new 来作为构造器的名称,出于设计上的考虑,Rust 特地没有用 new 作为关键字。
为之前的 Rectangle 创建一个关联函数,如:
1 | impl Rectangle { |
因为是函数,所以不能用 . 的方式来调用,我们需要用 :: 来调用,例如 let sq = Rectangle::new(3, 3);。这个方法位于结构体的命名空间中::: 语法用于关联函数和模块创建的命名空间。
多个 impl 定义
Rust 允许我们为一个结构体定义多个 impl 块,目的是提供更多的灵活性和代码组织性, 如:
1 | impl Rectangle { |
为枚举实现方法
我们也可以为枚举实现方法,如:
1 | #![allow(unused)] |