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
的值是 5
if
来赋值时,要保证每个分支返回的类型一样(事实上,这种说法不完全准确,见这里),此处返回的 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
并打印出来。
这里有几点值得注意:
return
match
的通用形式:
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
允许匹配一种模式,而忽略其余的模式( 可驳模式匹配 )。