Rust 语法
Rust是基于表达式的语言
表达式返回一个值,而语句不返回,rust中除两种语句外,全是 表达式
let
引入绑定- 可变绑定赋值是表达式,返回空tuple
- 声明后初始化绑定?#todo
- 表达式语句:表达式后跟分号转换为语句
代码中rust希望语句后跟语句,使用分号分隔表达式,所以rust 看起来和其他大部分分号结尾语言相似
{}
包裹的代码块内最后一“句”没有以”;”结尾,那么是表达式,且
返回该表达式的值,整个代码块可以看作是表达式,否则为语句,
没有返回值,函数同
模式匹配
Refutability(可反驳性)
- refutable(可反驳的):对某些可能值匹配失败的模式,
if let
、while let
只能接受可反驳的模式,因为这就用于 处理可能的失败 - irrefutable(不可反驳的):能匹配任何传递的可能值,
let
语句、函数参数、for
循环只能接受不可反驳的模式,因为 通过不匹配值的程序无意义
可能值是指“类型”相同,可用于匹配的值
1 | let Some(x) = some_optiona_value; |
Refutable
match
控制流
- 各分支模式同值“类型”必须完全一致才能匹配
- 返回类型必须一致,如果有返回值
- 匹配必须是穷尽的,可以使用通配符
_
(匹配所有的值)代替- match是匹配到就退出,不像switch一样会继续下沉
- 通配符不总是需要的,对于枚举类型只要含有所有枚举 成员的分支即可
1 | let x = Some(5); |
if let[else]
简洁控制流
- 只匹配关心的一个模式
- 可以添加
else
语句,类似match
通配符匹配 if let
、else if
、else if let
等相互组合可以提供更多 的灵活性else if
可以不是模式匹配- 各
if
后的比较的值可以没有关联
- 没有穷尽性检查,可能会遗漏一些情况
1 | fn main(){ |
Irrefutable
while let
条件循环
和if let
条件表达式类似,循环直到模式不匹配
1 | fn main(){ |
for
解构
1 | fn main(){ |
let
解构
let
语句本“应该“看作是模式匹配
1 | let PARTTERN = EXPRESSION; |
函数参数
类似于let
语句,函数参数也“应该”看作是模式匹配
1 | fn foo(x: i32){ |
模式匹配用法
|
、...
“或“
|
“或”匹配多个模式...
闭区间范围模式,仅适用于数值、char
值
1 | let x = 1; |
_
、..
”忽略“
_
忽略整个值1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20fn foo(_: i32, y: i32){
//函数签名中忽略整个值
println!("this code only use the y parameter: {}", y);
}
fn main(){
foo(3, 4);
let mut setting_value = Some(5); let new_setting_value = Some(10);
match (setting_value, new_setting_value){
(Some(_), Some()) => {
//嵌套`_`忽略部分值
//此时没有任何值所有权
println!("can't overwrite an exsiting customized value");
}
_ => {
setting_value = new_setting_value;
}
}
println!("setting is {:?}", setting_value);
}_var
变量名前添加下划线忽略未使用变量,此时值所有权 仍然会转移,只是相当于告诉编译器忽略该未使用变量1
2
3
4
5
6
7
8
9
10
11
12fn main(){
let _x = 5;
//两个变量未使用,但是编译时只有一个warning
let y = 10;
let s = Some(5);
if let Some(_s) = s {
//值所有权已转移,`s`不能继续使用
//编译器忽略未使用`_s`,不给出warning
println!("get a integer");
}
}..
忽略剩余值..
的使用必须无歧义- 对于结构体即使只有一个field,需使用
..
忽略剩余值, 不能使用_
1
2
3
4
5
6
7
8fn main(){
let numbers = (2, 4, 6, 8, 10);
match numbers{
(first, .., last) => {
println!("Some numbers: {}, {}", first, last);
}
}
}
1 | let (x, _, z) = (1, 2, 4); |
解构结构体
1 | struct Point{ |
解构枚举
1 | enum Message{ |
&
、ref
、ref mut
”引用“
&
匹配引用,“获得”值1
2
3
4
5
6
7
8
9
10
11let points = vec![
Point {x: 0, y: 0},
Point {x: 1, y: 5},
Point {x: 10, y: -3},
}
let sum_of_squares: i32 = points
.iter()
.map(|&Point {x, y}| x * x + y * y)
//`&`匹配一个引用
.sum();ref
匹配值,“获得”不可变引用1
2
3
4
5
6
7
8let robot_name = Some(String::from("Bors"));
match robot_name{
Some(ref name) => println!("found a name: {}", name),
//使用`ref`获取不可变引用才能编译成功
//否则所有权转移,之后报错
None => (),
}
println!("robot_name is: {:?}", robot_name);ref mut
匹配值,“获得”可变引用1
2
3
4
5
6let robot_name = Some(String::from("Bors"));
match robot_name{
Some(ref mut name) => *name = String::from("NewName"),
None => (),
}
println!("robot_name is: {:?}", robot_name);
if
match guard
匹配守卫match guard:放在=>
之前的if
语句,match
分支的额外条件,条件为真才会继续执行分支代码
1 | let num = Some(4); |
@
绑定
@
允许在创建存放值的变量时,同时测试值是否匹配模式
1 | enum Message{ |
闭包closures和函数指针
闭包是可以保存进变量或作为参数传递给其他函数的匿名函数, 可以在一个地方创建闭包,而在不同的上下文执行闭包。和函数 的区别在于,其可以捕获调用者作用域中的值,当然这会有性能 损失,如果不需要捕获调用者作用域中的值可以考虑使用函数
1 | let closures = |param1, param2|{ |
- 闭包参数:调用者使用,
- 创建闭包赋值给变量,再通过变量调用闭包
- 创建闭包作为参数传递,其他函数调用
- 捕获环境变量:创建闭包作为参数传递,直接使用周围环境变量
闭包类型推断和注解
闭包不要求像函数一样需要在参数和返回值上注明类型,函数需要 类型注解因为其是需要暴露给的显示接口的一部分,而闭包不用于 作为对外暴露的接口
- 作为匿名函数直接使用,或者存储在变量中
- 通常很短,使用场景上下文比较简单,编译器能够推断参数和 返回值类型
当然,闭包也可以添加注解增加明确性
1 | fn add_one_v1 {x: u32} -> u32 { x + 1 }; |
Rust会根据闭包出调用为每个参数和返回值推断类型,并将其锁定, 如果尝试对同一闭包使用不同类型的参数调用会报错
1 | let example_closure = |x| x; |
Fn
trait bound
每个闭包实例有自己独有的匿名类型,即使两个闭包有相同的签名, 其类型依然不同。为了定义使用闭包的结构体、枚举、函数参数, (这些定义中都需要指定元素类型),需要使用泛型和trait bound
FnOnce
:获取从周围环境捕获的变量的所有权,因此只能调用 一次,即Once
的含义Fn
:获取从周围环境捕获的变量的不可变引用FnMut
:获取从周围环境捕获的变量的可变引用
所有的闭包都实现了以上3个trait中的一个,Rust根据闭包如何 使用环境中的变量推断其如何捕获环境,及实现对应的trait
1 | struct Cacher<T> |
Function Pointer fn
1 | fn add_one(x: i32) -> i32{ |
函数指针类型实现了以上全部Fn
、FnMut
、FnOnce
三个
trait,所以总是可以在调用期望闭包作为参数的函数时传递函数
指针,因此倾向于使用泛型和闭包trait bound的函数,这样可以
同时使用闭包、函数指针作为参数。
1 | let list_of_numbers = vec![1, 2, 3]; |
与不存在闭包的外部代码(如C语言,只有函数没有闭包)交互时, 只能使用函数作为参数,不能使用闭包。
move
关键字
move
关键字强制闭包获其捕获的环境变量的所有权,在将闭包
传递给新线程以便将数据移动到新线程时非常实用
1 | fn main(){ |
返回闭包
闭包表现为trait,没有确定的类型、大小,无法直接返回,也不
允许使用函数指针fn
作为返回值类型,需要使用trait对象返回
闭包
1 | fn return_closure() -> Box<Fn(i32) -> i32>{ |
迭代器Iterator
迭代器负责遍历序列中的每一项和决定序列何时结束的逻辑。Rust 中迭代器时惰性的,直到调用方法”消费“迭代器之前都不会有效果
Iterator
trait
迭代器都实现了标准库中Iterator
trait
1 | trait Iterator{ |
next
方法
next
是Iterator
唯一要求被实现的方法,其返回迭代器中封装
在Some
中的一项(消费迭代器中的一项),迭代器结束时,
返回None
。
1 |
|
消费适配器Comsuming Adaptors
Iterator
trait中定义,调用next
方法,消耗迭代器
1 |
|
迭代器适配器Iterator Adaptors
Iterator
trait中定义,将当前迭代器变为其他迭代器,同样是
惰性的,必须调用消费适配器以便获取迭代适配器的结果
1 | let v1:Vec<i32> = vec![1, 2, 3]; |
1 |
|
实现Iterator
1 | struct Counter{ |
并发(并行)
多线程可以改善性能,但是也会增加复杂性
- 竞争状态Race Conditions:多个线程以不一致的顺序访问资源
- 死锁Dead Lock:线程互相等待资源释放,阻止继续运行
- 只会在特定情况出现、无法稳定重现的bug
线程模型
1:1
模型:一个OS线程对应一个语言线程,语言调用操作系统 API创建线程,性能较好M:N
模型:语言有自己的线程实现,其提供的线程称为 绿色(green)线程,M个绿色线程对应N个OS线程,更好的 运行控制、更底的上下文切换成本
Rust为了更小的运行时(这里表示二进制文件中语言自身提供的
代码)考虑,标准库中只提供了1:1
线程模式实现。可以通过一些
crate扩展M:N
线程模式。
spawn
创建新线程
std::thread::spawn
接受一个闭包作为参数,返回JoinHandle
类型的句柄。作为spawn
参数的闭包和一般的闭包有些不同,
线程直接独立执行,所以此时闭包捕获外部环境变量不能按照默认
的获取不可变引用,因为此时捕获的变量值可能已经被丢弃,必须
使用move
关键字获取所有权,而一般的闭包是顺序执行的,没有
特殊需要可以直接获取不可变引用,而能够保证值不被丢弃。
1 | use std::thread; |
消息传递
Rust中实现消息传递并发的主要工具是通道(channel)
mpsc::channel
mpsc:multiple producer single consumer,多个生产者,单个 消费者,即Rust标准库实现通道的方式允许多个产生值的发送端,但 只能有一个消费这些值的接收端。发送端或接收端任一被丢弃时, 意味着通道被关闭
1 | use std::thread; |
共享状态
(任何语言)通道都类似于单所有权,一旦值通过通道传送,将无法 再次使用,而共享内存类似于多所有权
Mutex<T>
互斥器mutex:mutual exclusion,任意时刻只允许一个线程访问 数据
- 线程在访问数据之前需要获取互斥器的锁lock,lock是作为 互斥器一部分的数据结构,记录数据所有者的排他访问权
- 处理完互斥器保护的数据之后,需要解锁,这样才能允许其他 线程获取数据
Mutex<T>
类似于线程安全版本的RefCell<T>
(cell
族),
提供了内部可变性,Mutex<T>
有可能造成死锁,如一个操作需要
两个锁,两个线程各持一个互相等待。
Arc<T>
原子引用计数atomically reference counted,则是
线程安全版本的Rc<T>
,而线程安全带有性能惩罚,如非必要,
使用单线程版本Rc<T>
性能更好。
1 | use std::sync::{Mutex, Arc}; |
Sync
trait、Send
trait
Rust的并发模型大部分属于标准库的实现,但是
std::marker::Send
和std::marker::Sync
时内嵌于语言的
Send
:表明类型所有权可以在线程间传递,几乎所有类型都是Send
的,Rc<T>
是其中的一个例外,因为Rc<T>
clone之后 在两个线程间可能同时更新引用计数,trait bound保证无法将 不安全的Rc<T>
在线程间传递。任何全部由Send
组成的类型 会自动标记为Send
Sync
:表明类型可以安全的在多线程中拥有其值的引用,对于 任何类型,如果&T
是Send
的,那么T
就是Sync
的。Cell<T>
系列不是Sync
的,Mutex<T>
是。基本类型是Sync
的,全部由Sync
组成的类型也是Sync
的
Send
和Sync
是标记trait,不需要实现任何方法,全部是Send
或Sync
组成的类型就是Send
或Sync
,一般不需要手动实现
它们,而且手动实现这些标记trait涉及编写不安全代码