在 C 语言中,表达式有值,而语句没有。
// 表达式
5 * (fahr - 32) / 9
// 语句
for (; begin != end; ++begin) {
if (*begin == target)
break;
}
Rust 是表达式语言
if
和 match
可以产生值
pixels[r * bounds.0 + c] =
match escapes(Complex { re: point.0, im: point.1 }, 255) {
None => 0, // 如果匹配None,则值为0
Some(count) => 255 - count as u8 // 如果匹配Some(count),则值为u8类型的数值
};
if
表达式可以用来初始化变量
let status =
if cpu.temperature <= MAX_TEMP {
// 条件成立,那么赋值为HttpStatus::Ok
HttpStatus::Ok
} else {
// 条件不成立,那么赋值为HttpStatus::ServerError
HttpStatus::ServerError // 服务器错误
};
match
表达式可以作为参数传递给函数或宏
println!("Inside the vat, you see {}.",
match vat.contents {
Some(brain) => brain.desc(),
None => "nothing of interest"
}
);
Rust 没有 C 语言的三元操作符(expr1 ? expr2 : expr3),完全可以通过上述实现代替。
Rust 的代码块,同样也是表达式,可以产生值:在代码块的最后一行省略分号,就会让这个块产生一个值
let display_name = match post.author() {
Some(author) => author.name(),
None => {
let network_info = post.get_network_metadata()?;
let ip = network_info.client_address();
ip.to_string() // 结尾没有分号
}
};
在 Rust 中,分号是有特殊意义的:
let msg = {
// let声明:分号是必须有的
let dandelion_control = puffball.open();
// 表达式 + 分号:方法调用,返回值被清除
dandelion_control.release_all_seeds(launch_codes);
// 表达式不带分号:方法被调用,返回值保存于msg中
dandelion_control.get_status()
};
技巧:当编译时,只要看到 expected type '() ,'
,首先看看是不是漏掉了分号。
空语句,就是一个孤立的分号:
loop {
work();
paly();
; // 空语句
}
let
常用于声明变量,语法为:
let name: type = expr;
// 类型type和初始值expr是可选的,分号是必须的。
let
声明可以只声明变量而不初始化它。之后可以通过赋值来初始化变量。这种情况常用于控制流结构中,可以在中间操作初始化变量:
let name; // 只声明了变量
if user.has_nickname() { // 通过if条件语句,为变量赋值初始化
name = user.nickname();
} else {
name = generate_unique_name();
user.register(&name);
}
在初始化之前使用变量是错误的。
Rust 支持重新声明一个已有变量:
for line in file.lines() {
let line = line?;
}
上述代码等价于:
for line_result in file.lines() {
let line = line_result?; // ?用于检查可能失败的函数调用
}
// _result作为后缀,使得变量的类型是Result
// 而第二个变量line的类型是String
块里也可以包含特性项声明:可以在程序或模块的全局中出现的声明,比如 fn
、struct
或 use
。
if
表达式
bool
型的表达式if condition1 {
block1
} else if condition2 {
block2
} else {
block_n
}
if
表达式中,所有的块 block
都必须产生相同类型的值
match
表达式,等同于 C 中的 switch
语句
可以有多个表达式分支,但是只有 1 个会执行
match code {
0 => println!("OK"),
1 => println!("Wires Tangled"),
2 => println!("User Asleep"),
_ => println!("Unrecognized Error {}", code)
}
_
表示通配模式,可以匹配任何值,相当于 C 语言 switch
语句的 default
条件。
match
表达式支持模式,常用于:
match
表达式的通用语法形式:
match value {
pattern => expr,
...
}
expr
是一个块,后面的逗号可以去掉if let
表达式:
if let pattern = expr {
block1
} else {
block2
}
实现了一种模式匹配,expr
要么匹配 pattern
,然后运行 block1
;
要么不匹配 pattern
,而运行 block2
。
常用于从 Option
或 Result
中取得数据:
if let Some(cookie) = request.session_cookie {
return restore_session(cookie);
}
if let Err(err) = present_cheesy_anti_robot_task() {
log_robot_attempt(err);
politely_accuse_user_of_being_a_robot();
} else {
session.mark_as_human();
}
if let
表达式是对只有一个模式的 match
表达式的简写:
match expr {
pattern => { block1 }
_ => { block2 }
}
()
。语法:
while codition {
block
}
codition
必须是 bool
类型。
语法:
while let pattern = expr {
block
}
与 if let
类似,在每次循环开始时,expr
的值要先匹配给定的 pattern
,再运行后面的块;如果不匹配,那么会退出循环。
语法:
loop {
block
}
用于编写无穷循环。
block
会永远重复执行break
return
语法:
for pattern in collection {
block
}
先对 collection
表达式求值,然后该集合中每个值必须对 block
求值
支持的集合有很多种:
..
操作符:产生一个范围(range),即一个拥有两个字段(start 和 end)的简单结构体。
for i in 0..20 {
println!("{}", i);
}
// 0..20等价于std::ops::Range { start: 0, end: 20 }
// Range是可迭代类型
标准的集合,如数组和切片,都是可迭代的:每迭代一个值就用掉一个值
let strings: Vec<String> = error_message();
for s in strings {
println!("{}", s);
}
println!("{} error(s)", strings.len());
迭代对集合的引用:循环变量就是对集合中每一项的引用
for rs in &strings {
println!("String {:?} is at address {:p}.", *rs, rs);
}
// &strings的类型是&Vec
// rs的类型是&String。
迭代 mut
引用:循环变量拿到的也是 mut
引用
for rs in &mut strings { // rs的类型是&mut String
rs.push('\n'); // 给每个字符串添加一个换行符
}
break
表达式用于退出闭合循环。match
表达式中用不到 break
。continue
表达式用于跳到循环的下一次迭代:
// 读取数据,每次读一行
for line in input_lines {
let trimmed = trim_comments_and_whitespace(line);
if trimmed.is_empty() {
// 跳到循环顶部,取得输入的下一行
continue;
}
...
}
在 for
循环中:
continue
会前进到集合中的下一个值。在 while
循环中:
continue
会再次检查循环条件。循环可以加上生命期标签:
'search: // 外部for循环的生命期标签
for room in apartment {
for spot in room.hiding_spots() {
if spot.contains(keys) {
println!("Your keys ar {} in the {}.", spot, room);
break 'search; // 此处表示break会退出外部循环,而不是内部循环。
}
}
}
生命期标签也可以与 continue
一起使用
return
表达式可以退出当前函数,并向调用者返回一个值。
无值 return
表达式是 return()
函数的简写:
fn f() { // 省略返回类型:默认为()
return; // 省略返回值:默认为()
}
return
也可以结束当前的工作,等同于 break
。
?
操作符:检查可能失败的函数调用。
let output = File::create(filename)?;
等价于如下 match
表达式代码:
let output = match File::create(filename) {
Ok(f) => f, // 匹配后,f会保存在output中
Err(err) => return Err(err)
};
Rust 编译器分析控制流的机制 —— 流敏感(flow-sensitive)分析:
不正常结束的表达式,会被指定为特殊类型 !
,它们不受其他类型需要尊总的规则约束。如 std::process::exit()
的函数签名中可以看到 !
:
fn exit(code: i32) -> !
!
意味着 exit()
永远不会返回,它是一个发散函数(divergent function)。静态方法与非静态方法的区别:
Vec::new()
;my_vec.len()
。方法可以链式调用:
Iron::new(router).http("localhost:3000").unwrap();
用于函数调用或方法调用的语法,不能用于泛型 Vec
:
<
是一个小于操作符:return Vec<i32>::with_capacity(1000); // 错误:需要进行链式比较
let ramp = (0..n).collection<Vec<i32>>(); // 错误,同上
针对此种情况,Rust 建议使用::
,而不是
:
return Vec::<i32>::with_capacity(1000);
let ramp = (0..n).collection::<Vec<i32>>();
::<...>
称为极速鱼(turbofish)
也可以省略类型参数,让 Rust 编译器自行推断【推荐方法】:
return Vec::with_capacity(10);
let ramp: Vec<i32> = (0..n).collection();
.
操作符左侧的值,如果是一个引用或智能指针类型,那么它就会跟方法调用一样自动解引用。
方括号常用于访问数组、切片或向量中的元素:
pieces[i] // 数组元素
..
范围操作符允许省略两侧的操作数。
.. // 表示全部范围;
a.. // 表示起始于a:{start: a}
.. b // 表示终止与b-1:{end: b}
a.. b // 表示范围>=a,
采用经典分治法实现快速排序:
fn quicksort<T: ord>(slice: &mut [T]) {
if slice.len() <= 1 {
return;
}
// 将切片分成前、后两部分
let pivot_index = partition(slice);
// 递归对slice的前半部分,进行排序
quicksort(&mut slice[.. pivot_index]);
// 递归对slice的后半部分,进行排序
quicksort(&mut slice[pivot_index + 1 ..]);
}
&
和 &mut
。*
:用于访问引用指向的值。只需读或写引用指向的整个值。.
点操作符会自动跟踪引用去访问字段或方法。算数操作符 | 说明 |
---|---|
+ |
加 |
- |
减 |
* |
乘 |
/ |
除 |
% |
取模,或取余 |
位操作符 | 说明 |
---|---|
& | 位与 |
| | 位或 |
^ | 位异或 |
! | 位非 |
<< | 左移 |
>> | 右移 |
!n
表示 “n 是 0”,需要写成 n == 0
。比较操作符 | 说明 |
---|---|
== | 等于 |
!= | 不等于 |
< | 小于 |
> | 大于 |
<= | 小于等于 |
>= | 大于等于 |
bool
类型。短路逻辑操作符 | 说明 |
---|---|
&& | 逻辑与 |
|| | 逻辑或 |
! | 逻辑非 |
=
赋值操作符,用于把值赋给 mut
变量,以及他们的字段或元素。
变量默认是不可修改的。
赋值会转移非可赋值类型的值。
Rust 支持复合赋值:
+=
-=
*=
/=
%=
<<=
>>=
&=
^=
|=
Rust 不支持链式赋值,如 C 语言中的 a=b=3
。
Rust 没有 C 语言中的递增操作符 ++
和递减操作符 --
Rust 中,将一个值从一种类型,转换为另一种,需要做显示转换。
as
操作符,实现类型转换。
允许的类型转换:
bool
、char
或类 C 的 enum
类型的值,可以转换为任何整数类型。把 mut
引用转换为非 mut
引用,会直接转换,不需要显示进行。
以下是一些比较重要的
自动转换
,被称为
解引用强制转换(deref corecion)
,适用于实现内置的
Deref
特型的类型。
&String
类型的值会自动换换为 &str
类型。&Vec
类型的值会自动转换为 &[i32]
。&Box
类型的值会自动转换为 &Chessboard
。Rust 的闭包,类似轻量级函数,通常由参数列表(在两条竖线中给出)和表达式组成:
let is_even = |x| x % 2 == 0;
Rust 会推断闭包的参数类型和返回类型。
如果明确指定了返回类型,那么闭包体必须是一个代码块(用 {}
括起来)。
let is_even = |x: u64| -> bool x % 2 == 0; // 错误
let is_even = |x: u64| -> bool {x % 2 == 0}; // 可以
调用闭包,与调用函数的语法相同:
assert_eq!(is_even(14), true);
优先级列表,按照从高到低列出:
表达式类型 | 举例 |
---|---|
数组字面量 | [1, 2, 3] |
重复的数组字面量 | [0; 50] |
元组 | (6, "crullers") |
分组 | (2 + 2) |
块 | {f(); g()} |
控制流表达式 | if ok {f();} |
宏调用 | println!("ok"); |
路径 | std::f64::consts::PI |
结构体字面量 | Point {x: 0, y: 0} |
元组字段存取 | pair.0 |
结构体字段存取 | point.x |
方法调用 | point.translate(50, 50) |
函数调用 | stdin() |
索引 | arr[0] |
错误检查 | create_dir("tmp")? |
逻辑 / 按位非 | !ok |
取反 | -num |
解引用 | *ptr |
借用 | &val |
类型转换 | x as u32 |
乘 | n * 2 |
除 | n / 2 |
取余(取模) | n % 2 |
加 | n + 2 |
减 | n - 2 |
左移 | n << 1 |
右移 | n >> 1 |
按位与 | n & 1 |
按位异或 | n ^ |
按位或 | `n |
小于 | n < 1 |
小于等于 | n <= 1 |
大于 | n > 1 |
大于等于 | n >= 1 |
等于 | n == 1 |
不等于 | n != 1 |
逻辑与 | x.ok && y.ok |
逻辑或 | `x.ok |
范围 | start.. stop |
赋值 | x = val |
复合赋值 | x += 1 |
闭包 | ` |
所有上述操作符,在链式操作时,都具有左关联性。
比较操作符、赋值操作符和范围操作符..
不能执行链式操作
详见《Rust 程序设计》(吉姆 - 布兰迪、贾森 - 奥伦多夫著,李松峰译)第六章
链接地址