表达式语言
Rust 是所谓的表达式语言。这意味着它遵循更古老的传统,可以追溯到 Lisp^[1]^,在 Lisp^[1]^ 中,表达式能完成所有工作。
在 C 中,if 和 switch 是语句,它们不生成值,也不能在表达式中间使用。而在 Rust 中,if 和 match 可以生成值。
例子 1 Rust 中的 If 直接生成了一个值
let status =
if cpu.temperature <= MAX_TEMP {
HttpStatus::Ok
} else {
HttpStatus::ServerError // 服务程序出错了
};
优先级与结合性
查看 附录 表达式优先级对照表
例子 2
* / % + - << >> & ^ | && || as
块与分号
块是一种最通用的表达式。一个块生成一个值,并且可以在任何需要值的地方使用.
例子 3
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()
}
};
ip.to_string()
就是最终的返回值
例子 4
fn main() {
let hello = {
let world = "world";
println!("Hello, {}!", world);
format!("Hello, {}!", world)
};
println!("{}", hello);
println!("{}", hello);
}
//输出
//Hello, world!
//Hello, world!
//Hello, world!
表示第一次 let hello
中的内容也会进行一次执行.
声明
除了表达式和分号,块还可以包含任意数量的声明。最常见的是 let 声明,它会声明局部变量.
let
声明可以在不初始化变量的情况下声明变量,然后再用赋值语句来初始化变量。这在某些情况下很有用,因为有时确实应该在某种控制流结构的中间初始化变量.
例子 5
let name;
if user.has_nickname() {
name = user.nickname();
} else {
name = generate_unique_name();
user.register(&name);
}
你可能偶尔会看到似乎在重新声明现有变量的代码.这叫作遮蔽(shadowing)
例子 6
for line in file.lines() {
let line = line?;
...
}
块还可以包含语法项声明(item declaration)。任何块都可能包含一个 fn.
例子 7
use std::io;
use std::cmp::Ordering;
fn show_files() -> io::Result<()> {
let mut v = vec![];
...
fn cmp_by_timestamp_then_name(a: &FileInfo, b: &FileInfo) -> Ordering {
a.timestamp.cmp(&b.timestamp) // 首先,比较时间戳
.reverse() // 最新的文件优先
.then(a.path.cmp(&b.path)) // 通过路径做二级比较
}
v.sort_by(cmp_by_timestamp_then_name);
...
}
if
与 match
match code
match
表达式类似于 C
语言中的 switch
语句,但更灵活.通配符模式 _
会匹配所有内容。
例子 8
match code {
0 => println!("OK"),
1 => println!("Wires Tangled"),
2 => println!("User Asleep"),
_ => println!("Unrecognized Error {}", code)
}
match 的多功能性源于每个分支 => 左侧支持的多种模式(pattern)。在上面的例子中,每个模式只是一个常量整数。我们还展示了用以区分两种 Option 值的 match 表达式
例子 9
match params.get("name") {
Some(name) => println!("Hello, {}!", name),
None => println!("Greetings, stranger.")
}
如果 expr 是一个块,则可以省略此分支之后的逗号。
[!IMPORTANT]
至少要有一个模式能够匹配。Rust 禁止执行未覆盖所有可能值的 match 表达式
if
和 match
表达式的所有块都必须生成相同类型的值.
例子 9
if let Some(cookie) = request.session_cookie {
return restore_session(cookie);
}
if let Err(err) = show_cheesy_anti_robot_task() {
log_robot_attempt(err);
politely_accuse_user_of_being_a_robot();
} else {
session.mark_as_human();
}
例子 10
for i in 0..20 {
println!("{}", i);
}
使用 for ... in ...
会消耗掉目标对象,所有我们需要使用引用方式去使用该内容.
例子 11
let strings: Vec<String> = error_messages();
for s in strings { // 在这里,每个String都会转移给s……
println!("{}", s);
} // ……并在此丢弃
println!("{} error(s)", strings.len()); // 错误:使用了已移动出去的值
//改成这样
for rs in &strings {
println!("String {:?} is at address {:p}.", *rs, rs);
}
//想要修改对象就是这样个样子
for rs in &mut strings { // rs的类型是&mut String
rs.push('\n'); // 为每个字符串添加一个换行
}
循环中的控制流
break
会退出当前循环.
例子 12
// 对`next_line`的每一次调用,或者返回一个`Some(line)`(这里的`line`是
// 输入中的一行),或者当输入已结束时返回`None`。最终会返回以"answer: "
// 开头的第1行,如果没找到,就返回"answer: nothing"
let answer = loop {
if let Some(line) = next_line() {
if line.starts_with("answer: ") {
break line;
}
} else {
break "answer: nothing";
}
};
continue
会跳转到循环的下次迭代
例子 13
// 读取某些数据,每次一行
for line in input_lines {
let trimmed = trim_comments_and_whitespace(line);
if trimmed.is_empty() {
// 跳转回循环的顶部,并移到输入中的下一行
continue;
}
...
}
循环可以带有生命周期标签。
例子 14
'search:
for room in apartment {
for spot in room.hiding_spots() {
if spot.contains(keys) {
println!("Your keys are {} in the {}.", spot, room);
break 'search;
}
}
}
return
表达式
return
表达式会退出当前函数,并向调用者返回一个值。
fn f() { // 省略了返回类型:默认为()
return; // 省略了返回值:默认为()
}
为什么 Rust 中会有 loop
Rust 编译器中有几个部分会分析程序中的控制流。
- Rust 会检查通过函数的每条路径是否返回了预期返回类型的值。为了正确地做到这一点,它需要知道是否有可能抵达函数的末尾。
- Rust 会检查局部变量有没有在未初始化的情况下使用过。这就要检查通过函数的每一条路径,以确保只要不经过初始化此变量的代码,就无法抵达使用它的地方。
- Rust 会对不可达代码发出警告。如果无法通过函数抵达某段代码,则这段代码不可达。
Rust 更倾向于简单性,它的流敏感分析根本不会检查循环条件,而会简单地假设程序中的任何条件都可以为真或为假。这会导致 Rust 可能拒绝某些安全程序.
fn wait_for_process(process: &mut Process) -> i32 {
while true {
if process.wait() {
return process.exit_code();
}
}
} // 错误:类型不匹配:期待i32,实际找到了()
[!CAUTION]
这里的
while true
rust 编译时会去验证 while 为 false 时是怎么样的,然后就导致编译不通过导致整体程序异常.
!
表示 exit()
永远不会返回,它是一个发散函数(divergent function)。
fn serve_forever(socket: ServerSocket, handler: ServerHandler) -> ! {
socket.listen();
loop {
let s = socket.accept();
handler.handle(s);
}
}
函数与方法调用
Rust 中调用函数和方法的语法与许多其他语言中的语法相同
let x = gcd(1302, 462); // 函数调用
let room = player.location(); // 方法调用
let mut numbers = Vec::new(); // 类型关联函数调用
server
.bind("127.0.0.1:3000").expect("error binding server to address")
.run().expect("error running server");
Rust 语法的怪癖之一就是,在函数调用或方法调用中,泛型类型的常用语法 Vec 是不起作用的:
return Vec<i32>::with_capacity(1000); // 错误:是某种关于“链式比较”的错误消息
let ramp = (0 .. n).collect<Vec<i32>>(); // 同样的错误
// 下面就是改正内容
return Vec::with_capacity(10); // 正确,只要fn的返回类型是Vec<i32>
let ramp: Vec<i32> = (0 .. n).collect(); // 正确,前面已给定变量的类型
字段与元素
你可以使用早已熟悉的语法访问结构体的字段。元组也一样,不过它们的字段是数值而不是名称.
game.black_pawns // 结构体字段
coords.1 // 元组元素
pieces[i] // 数组元素
这样的 3 个表达式叫作左值,因为赋值时它们可以出现在左侧.
game.black_pawns = 0x00ff0000_00000000_u64;
coords.1 = 0;
pieces[2] = Some(Piece::new(Black, Knight, coords));
从数组或向量中提取切片的写法很直观.
let second_half = &game_moves[midpoint .. end];
.. // RangeFull
a .. // RangeFrom { start: a }
.. b // RangeTo { end: b }
a .. b // Range { start: a, end: b }
引用运算符
地址运算符 &
和 &mut
.
赋值
=
运算符用于给 mut
变量及其字段或元素赋值。但是赋值在 Rust 中不像在其他语言中那么常见,因为默认情况下变量是不可变的。
total += item.price; //复合赋值
[!CAUTION]
Rust 没有 C 的自增运算符 ++ 和自减运算符 --。
类型转换
在 Rust 中,将值从一种类型转换为另一种类型通常需要进行显式转换。
let x = 17; // x是i32类型的
let index = x as usize; // 转换成usize
- &String 类型的值会自动转换为 &str 类型,无须强制转换。
- &Vec 类型的值会自动转换为 &[i32]。
- &Box 类型的值会自动转换为 &Chessboard。
闭包
Rust 也有 闭包
,即轻量级的类似函数的值。闭包通常由一个参数列表组成,在两条竖线之间列出,后跟一个表达式:
let is_even = |x| x % 2 == 0;
let is_even = |x: u64| -> bool x % 2 == 0; // 错误
let is_even = |x: u64| -> bool { x % 2 == 0 }; // 正确
assert_eq!(is_even(14), true);
附录
引用
- Lisp wiki - https://en.wikipedia.org/wiki/Lisp_(programming_language)
对照表
表达式优先级对照表
表达式类型 | 示例 |
---|---|
数组字面量 | [1, 2, 3] |
数组重复表达式 | [0; 50] |
元组 | (6, "crullers") |
分组 | (2 + 2) |
块 | { f(); g() } |
控制流表达式 | if ok { f() } if ok { 1 } else { 0 } if let Some(x) = f() { x } else { 0 } match x { None => 0, _ => 1 } for v in e { f(v); } while ok { ok = f(); } while let Some(x) = it.next() { f(x); } loop { next_event(); } break continue return 0 |
宏调用 | println!("ok") |
路径 | std::f64::consts::PI |
结构体字面量 | Point |
元组字段访问 | 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 + 1 |
减 | n - 1 |
左移 | n << 1 |
右移 | n >> 1 |
按位与 | n & 1 |
按位异或 | n ^ 1 |
按位或 | `n |
小于 | n < 1 |
小于等于 | n <= 1 |
大于 | n > 1 |
大于等于 | n >= 1 |
等于 | n == 1 |
等于 | n != 1 |
逻辑与 | x.ok && y.ok |
逻辑与 | `x.ok |
右开区间范围 | start .. stop |
右闭区间范围 | start ..= stop |
赋值 | x = val |
复合赋值 | x *=1 |
闭包 | ` |
评论