04 - 表达式
4.1 - 表达式语言
-
在 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),完全可以通过上述实现代替。
-
4.2 - 块与分号
-
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(); ; // 空语句 }
4.3 - 声明
-
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<String, io::Error> // 而第二个变量line的类型是String
-
-
块里也可以包含特性项声明:可以在程序或模块的全局中出现的声明,比如
fn
、struct
或use
。
4.4-if 与 match
4.4.1-if
-
if
表达式- 条件 condition 必须是一个
bool
型的表达式 - 条件 condition 可以不带圆括号。
if condition1 { block1 } else if condition2 { block2 } else { block_n }
- 条件 condition 必须是一个
-
if
表达式中,所有的块block
都必须产生相同类型的值
4.4.2-match
-
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
表达式支持模式,常用于:- 解包(unpack)元组
- 匹配结构体的个别字段
- 追索引用
- 借用一个值的某一部分
-
match
表达式的通用语法形式:match value { pattern => expr, ... }
- 如果
expr
是一个块,后面的逗号可以去掉 - 所有模式中必须至少有一个匹配项
- 所有分支都必须返回相同类型的值
- 如果
4.4.3-if let
-
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 } }
4.5 - 循环
- 循环在 Rust 中是表达式,但它不会产生有意义的值。
- 循环的值是基元
()
。
4.5.1-while
-
语法:
while codition { block }
-
codition
必须是bool
类型。
4.5.2-while let
-
语法:
while let pattern = expr { block }
-
与
if let
类似,在每次循环开始时,expr
的值要先匹配给定的pattern
,再运行后面的块;如果不匹配,那么会退出循环。
4.5.3-loop
-
语法:
loop { block }
-
用于编写无穷循环。
block
会永远重复执行- 直到遇到一个退出条件:
break
return
- 线程诧异
4.5.4-for
-
语法:
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<String> // rs的类型是&String。
-
迭代
mut
引用:循环变量拿到的也是mut
引用for rs in &mut strings { // rs的类型是&mut String rs.push('\n'); // 给每个字符串添加一个换行符 }
-
4.5.5-break
break
表达式用于退出闭合循环。- 只能在循环中使用,
match
表达式中用不到break
。
4.5.6-continue
-
continue
表达式用于跳到循环的下一次迭代:// 读取数据,每次读一行 for line in input_lines { let trimmed = trim_comments_and_whitespace(line); if trimmed.is_empty() { // 跳到循环顶部,取得输入的下一行 continue; } ... }
-
在
for
循环中:continue
会前进到集合中的下一个值。- 如果没有值了,则退出循环。
-
在
while
循环中:continue
会再次检查循环条件。- 如果为假,则退出循环。
4.5.7 - 循环的生命期
-
循环可以加上生命期标签:
'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
一起使用
4.6-return 表达式
-
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) };
-
4.7-Rust 的循环特点
-
Rust 编译器分析控制流的机制 —— 流敏感(flow-sensitive)分析:
- 检查贯穿函数的每条路径,确保返回值为正确类型;
- 检查局部变量永远不会在未初始化时被使用;
- 对无法抵达的代码给出警告。
-
不正常结束的表达式,会被指定为特殊类型
!
,它们不受其他类型需要尊总的规则约束。如std::process::exit()
的函数签名中可以看到!
:fn exit(code: i32) -> !
!
意味着exit()
永远不会返回,它是一个发散函数(divergent function)。
4.8 - 函数与方法调用
-
静态方法与非静态方法的区别:
- 静态方法通过类型调用,如
Vec::new()
; - 非静态方法通过值调用,如
my_vec.len()
。
- 静态方法通过类型调用,如
-
方法可以链式调用:
Iron::new(router).http("localhost:3000").unwrap();
-
用于函数调用或方法调用的语法,不能用于泛型
Vec<T>
:<
是一个小于操作符:
return Vec<i32>::with_capacity(1000); // 错误:需要进行链式比较 let ramp = (0..n).collection<Vec<i32>>(); // 错误,同上
-
针对此种情况,Rust 建议使用
::<T>
,而不是<T>:
: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();
4.9 - 字段与元素
-
.
操作符左侧的值,如果是一个引用或智能指针类型,那么它就会跟方法调用一样自动解引用。 -
方括号常用于访问数组、切片或向量中的元素:
pieces[i] // 数组元素
- 方括号左侧的值会自动解引用。
-
..
范围操作符允许省略两侧的操作数。.. // 表示全部范围; a.. // 表示起始于a:{start: a} .. b // 表示终止与b-1:{end: b} a.. b // 表示范围>=a,<b:{start: a, end: b}
- 返回是 ** 半开口(half-open)** 的,包含起始值,不包含结尾值。
- 只有包含起始值的范围才是可迭代的,因为循环必须从某个地方开始。
-
采用经典分治法实现快速排序:
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 ..]); }
4.10 - 引用操作符
- 取地址操作符:
&
和&mut
。 - 一元操作符
*
:用于访问引用指向的值。只需读或写引用指向的整个值。 .
点操作符会自动跟踪引用去访问字段或方法。
4.11 - 其他操作符
4.11.1 - 算术
算数操作符 | 说明 |
---|---|
+ | 加 |
- | 减 |
* | 乘 |
/ | 除 |
% | 取模,或取余 |
4.11.2 - 位
位操作符 | 说明 |
---|---|
& | 位与 |
| | 位或 |
^ | 位异或 |
! | 位非 |
<< | 左移 |
>> | 右移 |
- 不能用
!n
表示 “n 是 0”,需要写成n == 0
。 - 位操作的优先级高于比较操作。
4.11.3 - 比较
比较操作符 | 说明 |
---|---|
== | 等于 |
!= | 不等于 |
< | 小于 |
> | 大于 |
<= | 小于等于 |
>= | 大于等于 |
4.11.4 - 逻辑
- 短路逻辑操作符的操作数,必须都是
bool
类型。
短路逻辑操作符 | 说明 |
---|---|
&& | 逻辑与 |
|| | 逻辑或 |
! | 逻辑非 |
4.11.5 - 赋值
-
=
赋值操作符,用于把值赋给mut
变量,以及他们的字段或元素。 -
变量默认是不可修改的。
-
赋值会转移非可赋值类型的值。
-
Rust 支持复合赋值:
+= -= *= /= %= <<= >>= &= ^= |=
-
Rust 不支持链式赋值,如 C 语言中的
a=b=3
。 -
Rust 没有 C 语言中的递增操作符
++
和递减操作符--
4.12 - 类型转换
-
Rust 中,将一个值从一种类型,转换为另一种,需要做显示转换。
-
as
操作符,实现类型转换。 -
允许的类型转换:
- 数值可以从任何内置的数值类型转换为任意其他类型。
bool
、char
或类 C 的enum
类型的值,可以转换为任何整数类型。- 有些涉及不安全指针类型的转换是允许的。
-
把
mut
引用转换为非mut
引用,会直接转换,不需要显示进行。 -
以下是一些比较重要的
自动转换
,被称为
解引用强制转换(deref corecion)
,适用于实现内置的
Deref
特型的类型。
&String
类型的值会自动换换为&str
类型。&Vec<i32>
类型的值会自动转换为&[i32]
。&Box<Chessboard>
类型的值会自动转换为&Chessboard
。
4.13 - 闭包
-
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);
4.14 - 优先级
-
优先级列表,按照从高到低列出:
表达式类型 举例 数组字面量 [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 程序设计》(吉姆 - 布兰迪、贾森 - 奥伦多夫著,李松峰译)第六章
链接地址