LISP程序员知道一切的价值, 但不了解其代价. ----Alan Perlis
6.1 表达式语言
Rust是表达式语言, 这意味着:
- 虽然Rust严格区分语句(Statement)和表达式(Expression)
- 但是所有的代码结构都可以是表达式(Expression), 也可以转化为语句(Statement)从而抛弃表达式返回的值
- 比如: if在C语言中没有返回值 (用Rust的思维来理解: 强制为statement), 但是在Rust中可以是表达式
这也是Rust没有三元运算符的原因: 可以直接用if表达式代替
fn main() {
let status = if (true) {
1
} else {
0
};
}
6.2 优先级与结合性
结合性: 所有可以链式书写的运算符都是左结合的
* / % + - << >> & ^ | && || as
均为左结合
重点优先级关系:
- 访问运算符
.
> 解引用运算符*
- 算数运算符 > 位运算符 > 比较运算符 > 逻辑运算符 > 赋值运算符
表达式类型 | 示例 | 相关特性 |
---|---|---|
数组字面量 | [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 | std::iter::IntoIterator |
宏调用 | println!("ok") | |
路径 | std::f64::consts::PI | |
结构体字面量 | Point {x: 0, y: 0} | |
元组字段访问 | pair.0 | Deref, DerefMut |
结构体字段访问 | point.x | Deref, DerefMut |
方法调用 | point.translate(50, 50) | Deref, DerefMut |
函数调用 | stdin() | Fn(Arg0, ...) -> T, FnMut(Arg0, ...) -> T, FnOnce(Arg0, ...) -> T |
索引 | arr[0] | Index, IndexMutDeref, DerefMut |
错误检查 | create_dir("tmp")? | |
逻辑非/按位非 | !ok | Not |
取负 | -num | Neg |
解引用 | *ptr | Deref, DerefMut |
借用 | &val | |
类型转换 | x as u32 | |
乘 | n * 2 | Mul |
除 | n / 2 | Div |
取余(取模) | n % 2 | Rem |
加 | n + 1 | Add |
减 | n - 1 | Sub |
左移 | n << 1 | Shl |
右移 | n >> 1 | Shr |
按位与 | n & 1 | BitAnd |
按位异或 | n ^ 1 | BitXor |
按位或 | n | 1 | BitOr |
小于 | n < 1 | std::cmp::PartialOrd |
小于等于 | n <= 1 | std::cmp::PartialOrd |
大于 | n > 1 | std::cmp::PartialOrd |
大于等于 | n >= 1 | std::cmp::PartialOrd |
等于 | n == 1 | std::cmp::PartialEq |
不等于 | n != 1 | std::cmp::PartialEq |
逻辑与 | x.ok && y.ok | |
逻辑或 | `x.ok | |
闭包 | |x, y| x + y |
6.3 块与分号
- 块本质: 表达式 (一个块生成一个值)
- 块的值: 块表达式的值 = 最后一个表达式的值 (类似于函数返回)
- 块中表达式均有
;
, 返回()
- 块中表达式均有
- 分号:
- lei声明必须带分号
- 带分号表达式: 调用方法, 丢弃返回值
Some(anthor) =>
后续是一个普通表达式None =>
后续是块表达式, 块表达式的值 = 最后一个表达式的值
fn main() {
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()
}
}
}
使用if返回值必须包含else, 保证变量始终能接受确定的值
if preference.changed() {
page.compute_size()
} // 错误: 缺少else
6.4 声明
变量名约定:
- 可以包含更多的字符, 包含希腊字母, 中文
- 不允许使用表情符号
let
声明可以不初始化 (编写过程中vscode实时检查会报错)let
允许对变量赋值一次(就是初始化), 所以后续无需let mut
也可以赋值一次
let 变量名: 类型 = 表达式;
6.4.1 遮蔽 (shadowing)
处理遮蔽: 内部遮蔽外部
for line in file.lines() { // 外部line
let line = line?; // 内部声明line, 从此出开始, 后续内部line遮蔽外部
...
}
6.4.1 语法项声明
- 语法项(syntax items): 可以在程序或模块中的任意地方出现的声明 (比如:
fn
,struct
,use
) - 语法项声明(item declaration): rust可以在块中语法项声明, 比如在块中通过
fn
声明一个函数. (其作用域只能在块内)
6.5 if, match与循环
6.5.1 if语句
语法约定:
- condition1必须是bool型
- if不需要圆括号
else if
和else
可选- 块的本质是表达式, if表达式的块必须生成相同类型的值, 如果没有else或者没有返回值, 默认生成零元组
()
if condition1 {
block1
} else if condition2 {
block2
} else {
block_n
}
6.5.2 match
语法约定:
- 类似于switch
- 更优秀的点: 左侧支持多种pattern, 而不仅仅只是常量 (比如: Option<T>, 解构元组, 元组各个字段等)
- 最大的不同: =>后续是表达式(Expression), 而不是单独的语句(Statement), 并使用逗号分隔
- 限制: 与if类似, 返回的表达式必须是相同类型的
- 基本语法:
- default改为_
- :改为=>
- 优先级: 从上到下递减, 所以不能将
_
放在靠前的位置, 导致优先匹配_
// 一般格式
match value {
pattern => expression,
...
}
// 实例
match flag {
0 => println!("OK"),
1 => println!("Wires Tangled"),
_ => println!("Error")
}
6.5.3 if let
- 本质: match的简写
- if和if let的区别: pattern可以进行模式匹配 (实现: Option或者Result提取数据)
// if-let
if let pattern = expr {
block1
} else {
block2
}
// 等效match
match expr {
pattern => { block1 }
_ => { block2 }
}
6.5.4 循环
四种循环
- 其中
..
表示生成一个range, 即一个结构体(start, end)
(比如:0..20
表示std::ops::Rangg {start: 0, end: 20}
且Range
是可迭代类型)
while condition {
block
} // 本质表达式: 返回()
while let pattern = expr {
block
} // 本质表达式: 返回()
loop { // 无限循环: 直到遇到break或者return
block
} // 本质表达式: 如果指定一个值, loop能生成该值
for pattern in iterable { // iterable表示可迭代
block
} // 本质表达式: 返回()
// 实例
for i in 0..20 { ... }
6.5.4.a for循环特性
for循环会消耗值
fn main() {
let strings: Vec<String> = vec!["Lame".to_string(), "Crow".to_string()];
for s in strings { // 相当于s = strings[i]
println!("{}", s);
}
println!("{} error(s)", strings.len()); // 错误: strings以及下属的元素的所有权均move给了for循环, 并消耗掉了
}
利用reference隐藏该特性
for rs in &strings {
println!("String {:?} is at address {:p}.", *rs, rs);
}
6.6 循环中的控制流
6.6.1 break和continue
- rust中的控制表达式:
break
,continue
(注意: break, continue等也是表达式) loop
与break
联合:break
的功能类似于return
//break
fn main() {
let a = 1;
let answer = loop {
if a == 1 {
break 111;
} else {
break 222;
}
};
}
// continue
fn main() {
for i in 0..50 {
if i % 2 == 0 {
continue;
}
println!("{}", i);
}
}
6.6.2 多重break和循环生命周期
通过给循环标注生命周期实现多重break
fn main() {
'ending: for i in 0..20 { // 生命周期标注
for j in 0..20 {
if i == 10 && j == 15 {
break 'ending 1; // 跳出外层for循环, 同时返回1
}
println!("({}, {})", i, j);
}
}
}
6.7 return表达式
- 默认返回: 或者默认情况返回
()
- 带值返回: 尾行省略分号, 直接给出表达式, 表示返回
- 上述两种情况都不需要使用
return
,return
主要用于提前返回的情况
6.8 异常控制流与!类型
- Rust采用流敏感(flow-sensitive)分析, 会检查每一个执行的分支.
- 且检查规则偏于简单性: 每个循环条件都会假设真和假两种情况
- Rust对于控制流的返回值类型也有约束 (比如: if要求所有分支返回相同类型), 但是在if内部中使用
break
,return
,panic!()
等方式结束时并不会要求相同类型, 这些表达式都属于特殊类型!
, 且不受类型匹配的约束.fn exit(code: i32) -> !
!
: 表示永远不会返回的类型, 而不是任意类型
fn wait(process: &mut Process) -> i32 {
while true {
if process.wait() {
return process.exit_code();
}
} // 即使条件为true, Rust仍会假设false情况返回了(), 与返回类型不符
} // 但事实上该函数没有问题, 因为实际控制流只可能在return处返回i32值
6.9 函数与方法调用
特性: 成员访问运算符.
会自动解析(多重)引用, 具体值或者智能指针
let x = gcd(2013, 432); // 函数调用
let room = player.location(); // 方法调用
let mut numbers = Vec::new(); // 类型关联函数调用 (类似静态方法)
server.bind("127.0.0.1:3000").expect("error binding").run().expect("error running"); // 链式调用
6.10 字段与元素
student.name // 结构体字段
coords.1 // 元组元素
pieces[1] // 方括号用于访问数组, 切片, 向量的元素
6.11 引用运算符
// 成员访问运算符: 主要用于成员访问, 元素访问, 方法调用
// 解引用运算符: 主要用于引用所指向的值本身
fn main() {
let padovan: Vec<u64> = vec![123, 321];
for elem in &padovan {
println!("{}", *elem); // 也可以省略
}
}
6.12 算数运算符, 位运算符, 比较运算符, 逻辑运算符
仅描述比较特殊的约定:
- 只有一元
-
, 没有一元+
- 除零产生panic
- Rust使用
!
表示按位非, 而不是~
- 导致非零不能表示为
!n
而是应该写作n == 0
- 导致非零不能表示为
- 移位总是对有符号进行符号扩展, 无符号进行零扩展
- 与C不同, Rust位运算优先级高于比较运算
- Rust比较运算符必须相同类型
6.13 赋值
- Rust默认情况下变量不可变
- Rust不支持链式赋值
- Rust没有自增和自减运算符
6.14 类型转换
- Rust类型转换需要显式转换
as
(比如:let index = x as usize;
) - 数值 -> 其他内置类型:
- 降低转换: 截断
- 提升扩展: 符号扩展或者零扩展
- 浮点->整数: 截断小数
- bool, char, enum可以转换为整数, 但不允许反向转换
- 引用的转换是非常宽松的
- &String --自动转换-> &str
- &Vec<i32> --自动转换-> &[i32]
- &Box<Chessboard> --自动转换-> &Chessboard
6.15 闭包
闭包: 轻量级类似于函数的表达式 (其本质是表达式)
// 参数和返回值的类型自动推断, 也可以显式给出
let is_even = |x| x % 2 == 0;
// 如果指定了返回类型, 为了语法完整性, 主体必须是一个块
lei is_even = |x: u64| -> bool { x % 2 == 0 };
特色语法汇总
一些零碎的, 比较有rust特色的语法汇总
语法 | 说明 | 代码 |
---|---|---|
break 和loop 联合 | loop 本身是一个表达式, 可以生成值, 具体值由break 返回 | |
? 运算符 | 对match 对简写形式, 主要用于对Option 和Result 模式匹配 | let a = File::create(filename)?; |
异常控制流与!类型 | 虽然if 要求所有分支返回相同类型值, 但是break , return , 无线loop , panic! , exit 可以放宽类型检查 | fn exit(code: i32) -> ! |
比目鱼语法 | 在Rust中调用泛型方法不能直接使用类似于Vec<T> 的语法, Rust会将其视为< 运算符, 应该使用::<...> 类似于比目鱼一样的语法. | return Vec::<i32>::func() 更好的代码风格: 让Rust自行推断 return Vec::func() |
.. 运算符 | start被包含, end则不被包含, 长度等于end - start 使用 ..= 可以包含end整个数组: .. a到结尾: a .. 开头到b: .. b a到b: a .. b | |
Rust没有自增和自减运算符 | ||
bool, char, enum可以转换为整数, 但不允许反向转换 |