if condition == true {
// A...
} else {
// B...
}
该代码读作:若 condition 的值为 true,则执行 A 代码,否则执行 B 代码。例如:
fn main() {
let condition = true;
let number = if condition {
5
} else {
6
};
println!("The value of number is: {}", number);
}
if 语句块是表达式,这里我们使用 if 表达式的返回值来给 number 进行赋值:number 的值是 5if 来赋值时,要保证每个分支返回的类型一样(事实上,这种说法不完全准确,见这里),此处返回的 5 和 6 就是同一个类型,如果返回类型不一致就会报错可以将 else if 与 if、else 组合在一起实现更复杂的条件分支判断
核心就在于 for 和 in 的联动,语义表达如下:
for 元素 in 集合 {
// 使用元素干一些你懂我不懂的事情
}
例如:
fn main() {
for i in 1..=5 {
println!("{}", i);
}
}
以上代码循环输出一个从 1 到 5 的序列。
注意,使用 for 时我们往往使用集合的引用形式,除非你不想在后面的代码中继续使用该集合(比如我们这里使用了 container 的引用)。如果不使用引用的话,所有权会被转移(move)到 for 语句块中,后面就无法再使用这个集合了)
如果想在循环中,修改该元素,可以使用 mut 关键字:
for item in &mut collection {
// ...
}
总结如下:
| 使用方法 | 等价使用方式 | 所有权 |
|---|---|---|
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() | 可变借用 |
如果想在循环中获取元素的索引:
fn main() {
let a = [4, 3, 2, 1];
// `.iter()` 方法把 `a` 数组变成一个迭代器
for (i, v) in a.iter().enumerate() {
println!("第{}个元素是{}", i + 1, v);
}
}
两种循环方式:
// 第一种
let collection = [1, 2, 3, 4, 5];
for i in 0..collection.len() {
let item = collection[i];
// ...
}
// 第二种
for item in collection {
}
第一种方式是循环索引,然后通过索引下标去访问集合,第二种方式是直接循环集合中的元素,优劣如下:
collection[index] 的索引访问,会因为边界检查(Bounds Checking)导致运行时的性能损耗 —— Rust 会检查并确认 index 是否落在集合内,但是第二种直接迭代的方式就不会触发这种检查,因为编译器会在编译时就完成分析并证明这种访问是合法的collection 的索引访问是非连续的,存在一定可能性在两次访问之间,collection 发生了变化,导致脏数据产生。而第二种直接迭代的方式是连续访问,因此不存在这种风险使用 continue 可以跳过当前当次的循环,开始下次的循环;使用 break 可以直接跳出当前整个循环
如果你需要一个条件来循环,当该条件为 true 时,继续循环,条件为 false,跳出循环,那么 while 就非常适用:
fn main() {
let mut n = 0;
while n <= 5 {
println!("{}!", n);
n = n + 1;
}
println!("我出来了!");
}
loop 就是一个简单的无限循环,你可以在内部实现逻辑通过 break 关键字来控制循环何时结束。当使用 loop 时,必不可少的伙伴是 break 关键字,它能让循环在满足某个条件时跳出:
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
println!("The result is {}", result);
}
以上代码当 counter 递增到 10 时,就会通过 break 返回一个 counter * 2 的值,最后赋给 result 并打印出来。
这里有几点值得注意:
returnmatch 的通用形式:
match target {
模式1 => 表达式1,
模式2 => {
语句1;
语句2;
表达式2
},
_ => 表达式3
}
该形式清晰的说明了何为模式,何为模式匹配:将模式与 target 进行匹配,即为模式匹配
match 的匹配必须要穷举出所有可能,因此这里用 _ 来代表未列出的所有可能性match 的每一个分支都必须是一个表达式,且所有分支的表达式最终返回值的类型必须相同match 本身也是一个表达式,因此可以用它来赋值:
enum IpAddr {
Ipv4,
Ipv6
}
fn main() {
let ip1 = IpAddr::Ipv6;
let ip_str = match ip1 {
IpAddr::Ipv4 => "127.0.0.1",
_ => "::1",
};
println!("{}", ip_str);
}
因为这里匹配到 _ 分支,所以将 "::1" 赋值给了 ip_str。
模式匹配的一个重要功能是从模式中取出绑定的值。
模式绑定的例子:
enum Action {
Say(String),
MoveTo(i32, i32),
ChangeColorRGB(u16, u16, u16),
}
fn main() {
let actions = [
Action::Say("Hello Rust".to_string()),
Action::MoveTo(1,2),
Action::ChangeColorRGB(255,255,0),
];
for action in actions {
match action {
Action::Say(s) => {
println!("{}", s);
},
Action::MoveTo(x, y) => {
println!("point from (0, 0) move to ({}, {})", x, y);
},
Action::ChangeColorRGB(r, g, _) => {
println!("change color into '(r:{}, g:{}, b:0)', 'b' has been ignored",
r, g,
);
}
}
}
}
运行后输出:
$ cargo run
Compiling world_hello v0.1.0 (/Users/sunfei/development/rust/world_hello)
Finished dev [unoptimized + debuginfo] target(s) in 0.16s
Running `target/debug/world_hello`
Hello Rust
point from (0, 0) move to (1, 2)
change color into '(r:255, g:255, b:0)', 'b' has been ignored
_通配符match 的匹配必须穷尽所有情况,即穷尽匹配,Rust 编译器清晰地知道 match 中有哪些分支没有被覆盖, 这种行为能强制我们处理所有的可能性。
通过将 _ 其放置于其他分支后,_ 将会匹配所有遗漏的值。() 表示返回单元类型与所有分支返回值的类型相同,所以当匹配到 _ 后,什么也不会发生。
有时会遇到只有一个模式的值需要被处理,其它值直接忽略的场景,如果用 match 来处理就要写成下面这样:
let v = Some(3u8);
match v {
Some(3) => println!("three"),
_ => (),
}
我们只想要对 Some(3) 模式进行匹配, 不想处理任何其他 Some 值或 None 值。但是为了满足 match 表达式(穷尽性)的要求,写代码时必须在处理完这唯一的成员后加上 _ => (),这样会增加不少无用的代码。
俗话说“杀鸡焉用牛刀”,我们完全可以用 if let 的方式来实现:
if let Some(3) = v {
println!("three");
}
这两种匹配对于新手来说,可能有些难以抉择,但是只要记住一点就好:当你只要匹配一个条件,且忽略其他条件时就用 if let ,否则都用 match。
无论是 match 还是 if let,他们都可以在模式匹配时覆盖掉老的值,绑定新的值:
fn main() {
let age=Some(30);
let b=3;
println!("匹配前b:{:?}",b); //3
if let b=age{
println!("b:{:?}",b); //Some(30)
}
println!("匹配后b:{:?}",b); //3
}
matches!宏可以将一个表达式跟模式进行匹配,然后返回匹配的结果 true or false。
例如:
let foo = 'f';
assert!(matches!(foo, 'A'..='Z' | 'a'..='z'));
let bar = Some(4);
assert!(matches!(bar, Some(x) if x > 2));
Opention枚举用来解决Rust中变量是否有值的问题,定义如下:
enum Option<T> {
Some(T),
None,
}
简单解释就是:一个变量要么有值:Some(T), 要么为空:None。
使用 Option,是为了从 Some 中取出其内部的 T 值以及处理没有值的情况,为了演示这一点,下面一起来编写一个函数,它获取一个 Option,如果其中含有一个值,将其加一;如果其中没有值,则函数返回 None 值:
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
plus_one 接受一个 Option 类型的参数,同时返回一个 Option 类型的值(这种形式的函数在标准库内随处所见),在该函数的内部处理中,如果传入的是一个 None ,则返回一个 None 且不做任何处理;如果传入的是一个 Some(i32),则通过模式绑定,把其中的值绑定到变量 i 上,然后返回 i+1 的值,同时用 Some 进行包裹。
模式是 Rust 中的特殊语法,它用来匹配类型中的结构和数据,模式一般由以下内容组合而成:
所有可能认识到模式的地方:
match VALUE {
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
}
如上所示,match 的每个分支就是一个模式,因为 match 匹配是穷尽式的,因此我们往往需要一个特殊的模式 _,来匹配剩余的所有情况:
match VALUE {
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
_ => EXPRESSION,
}
if let 往往用于匹配一个模式,而忽略剩下的所有模式的场景:
if let PATTERN = SOME_VALUE {
}
一个与 if let 类似的结构是 while let 条件循环,它允许只要模式匹配就一直进行 while 循环。下面展示了一个使用 while let 的例子:
// Vec是动态数组
let mut stack = Vec::new();
// 向数组尾部插入元素
stack.push(1);
stack.push(2);
stack.push(3);
// stack.pop从数组尾部弹出元素
while let Some(top) = stack.pop() {
println!("{}", top);
}
这个例子会打印出 3、2 接着是 1。pop 方法取出动态数组的最后一个元素并返回 Some(value),如果动态数组是空的,将返回 None,对于 while 来说,只要 pop 返回 Some 就会一直不停的循环。一旦其返回 None,while 循环停止。可以使用 while let 来弹出栈中的每一个元素。
let v = vec!['a', 'b', 'c'];
for (index, value) in v.iter().enumerate() {
println!("{} is at index {}", value, index);
}
这里使用 enumerate 方法产生一个迭代器,该迭代器每次迭代会返回一个 (索引,值) 形式的元组,然后用 (index,value) 来匹配。
let PATTERN = EXPRESSION;
是的, 该语句我们已经用了无数次了,它也是一种模式匹配:
let x = 5;
这其中,x 也是一种模式绑定,代表将匹配的值绑定到变量 x 上。因此,在 Rust 中,变量名也是一种模式,只不过它比较朴素很不起眼罢了。
let (x, y, z) = (1, 2, 3);
上面将一个元组与模式进行匹配(模式和值的类型必需相同!),然后把 1, 2, 3 分别绑定到 x, y, z 上。
模式匹配要求两边的类型必须相同,否则就会导致下面的报错:
let (x, y) = (1, 2, 3);
error[E0308]: mismatched types
--> src/main.rs:4:5
|
4 | let (x, y) = (1, 2, 3);
| ^^^^^^ --------- this expression has type `({integer}, {integer}, {integer})`
| |
| expected a tuple with 3 elements, found one with 2 elements
|
= note: expected tuple `({integer}, {integer}, {integer})`
found tuple `(_, _)`
For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground` due to previous error
对于元组来说,元素个数也是类型的一部分!
函数参数也是模式:
fn foo(x: i32) {
// 代码
}
其中 x 就是一个模式,你还可以在参数中匹配元组:
fn print_coordinates(&(x, y): &(i32, i32)) {
println!("Current location: ({}, {})", x, y);
}
fn main() {
let point = (3, 5);
print_coordinates(&point);
}
&(3, 5) 会匹配模式 &(x, y),因此 x 得到了 3,y 得到了 5。
对于以下代码,编译器会报错:
let Some(x) = some_option_value;
因为右边的值可能不为 Some,而是 None,这种时候就不能进行匹配,也就是上面的代码遗漏了 None 的匹配。
类似 let , for和match 都必须要求完全覆盖匹配,才能通过编译( 不可驳模式匹配 )。
但是对于 if let,就可以这样使用:
if let Some(x) = some_option_value {
println!("{}", x);
}
因为 if let 允许匹配一种模式,而忽略其余的模式( 可驳模式匹配 )。