原文
在Rust
中混合匹配,改变和移动
结构模式
匹配:极大的改进了C
或Java
风格的switch
语句.
Match
包含命令式和函数式
编程风格:可继续使用break
语句,赋值等,不必面向表达式
.
按需匹配"借用"或"移动"
,:Rust
鼓励开发者仔细考虑所有权和借用.设计匹配
时仅支持借用
子结构(而不是总移动
).
Rust
中的match(匹配)式
有以下形式:
match INPUT_EXPRESSION {
PATTERNS_1 => RESULT_EXPRESSION_1,
PATTERNS_2 => RESULT_EXPRESSION_2,
...
PATTERNS_n => RESULT_EXPRESSION_n
}
其中每个PATTERNS_i
至少包含一个模式.模式描述了INPUT_EXPRESSION
可计算到的可能值的子集.语法PATTERNS=>RESULT_EXPRESSION
叫"匹配分支",
或简叫"分支"
.
模式可匹配
如整数或符
等简单值;还可通过枚举
定义匹配
用户定义的符号数据
.
示例:
enum Answer {
Higher,
Lower,
Bingo,
}
fn suggest_guess(prior_guess: u32, answer: Answer) {
match answer {
Answer::Higher => println!("maybe try {} next", prior_guess + 10),
Answer::Lower => println!("maybe try {} next", prior_guess - 1),
Answer::Bingo => println!("we won with {}!", prior_guess),
}
}
#[test]
fn demo_suggest_guess() {
suggest_guess(10, Answer::Higher);
suggest_guess(20, Answer::Lower);
suggest_guess(19, Answer::Bingo);
}
模式还可用(如元组,切片,结构
)相应模式匹配
结构化数据.绑定部分输入
到局部变量
;然后,可在结果式
中使用这些变量.
struct GuessState {
guess: u32,
answer: Answer,
low: u32,
high: u32,
}
fn suggest_guess_smarter(s: GuessState) {
match s {
GuessState { answer: Answer::Bingo, guess: p, .. } => {
//..匹配值序列或名值对
println!("we won with {}!", p);
}
GuessState { answer: Answer::Higher, low: _, guess: l, high: h } |
GuessState { answer: Answer::Lower, low: l, guess: h, high: _ } => {
//_匹配单值,作为最后的默认.
//|表示`或者`.
let mid = l + ((h - l) / 2);
println!("lets try {} next", mid);
}
}
}
#[test]
fn demo_guess_state() {
suggest_guess_smarter(GuessState {
guess: 20, answer: Answer::Lower, low: 10, high: 1000
});
}
编译时拒绝以下代码
.
fn suggest_guess_broken(prior_guess: u32, answer: Answer) {
let next_guess = match answer {
Answer::Higher => prior_guess + 10,
Answer::Lower => prior_guess - 1,
//错误:未完整匹配.
};
println!("maybe try {} next", next_guess);
}
fn suggest_guess_fixed(prior_guess: u32, answer: Answer) {
let next_guess = match answer {
Answer::Higher => prior_guess + 10,
Answer::Lower => prior_guess - 1,
Answer::Bingo => {
println!("we won with {}!", prior_guess);
return;
}//补上最后分支
};
println!("maybe try {} next", next_guess);
}
#[test]
fn demo_guess_fixed() {
suggest_guess_fixed(10, Answer::Higher);
suggest_guess_fixed(20, Answer::Lower);
suggest_guess_fixed(19, Answer::Bingo);
}
代数数据
类型简要描述了数据类
,并允许丰富
的结构不变量
.
在Rust
中,枚举可定义更加丰富
的数据类
.
如,二叉树
或为叶
,或为引用两个子树
的内部节点
.构建树:
enum BinaryTree {
Leaf(i32),
Node(Box<BinaryTree>, i32, Box<BinaryTree>)
}
Box
描述了拥有堆分配的V实例
引用;如果拥有Box
,则也就拥有了它所包含的V
,且可改变它
,借出引用
等等.
完成Box
并出域
时,自动清理
与堆分配的V实例
关联的资源
.
上面的枚举定义
确保,如果得到一个BinaryTree
,将总是属于上述
二者之一.永远不会遇见无左子的BinaryTree::Node
.因此无需检查null
.
但确实
要检查给定的BinaryTree
是Leaf
还是Node
,但编译器会静态
确保此类检查:你不会意外
地按节点解释Leaf
数据,反之亦然.
如下使用match
对树中的所有整数
求和:
fn tree_weight_v1(t: BinaryTree) -> i32 {
match t {
BinaryTree::Leaf(payload) => payload,
BinaryTree::Node(left, payload, right) => {
tree_weight_v1(*left) + payload + tree_weight_v1(*right)
}
}
}
///返回如下的树:
/// +----(4)---+
/// | |
/// +-(2)-+ [5]
/// | |
/// [1] [3]
fn sample_tree() -> BinaryTree {
let l1 = Box::new(BinaryTree::Leaf(1));
let l3 = Box::new(BinaryTree::Leaf(3));
let n2 = Box::new(BinaryTree::Node(l1, 2, l3));
let l5 = Box::new(BinaryTree::Leaf(5));
BinaryTree::Node(n2, 4, l5)
}
#[test]
fn tree_demo_1() {
let tree = sample_tree();
assert_eq!(tree_weight_v1(tree), (1 + 2 + 3) + 4 + 5);
}
代数数据
类型创建语言严格执行的结构不变量
.
下面的代码使用区间
模式来简化,编写风格
类似面向语句语言(如C
(或C++,Java
等)中的开关(switch)
),其中仅针对该分支执行匹配
:
fn num_to_ordinal(x: u32) -> String {
let suffix;
match (x % 10, x % 100) {
(1, 1) | (1, 21...91) => {
suffix = "st";
}
(2, 2) | (2, 22...92) => {
suffix = "nd";
}
(3, 3) | (3, 23...93) => {
suffix = "rd";
}
_ => {
suffix = "th";
}
}
return format!("{}{}", x, suffix);
}
#[test]
fn test_num_to_ordinal() {
assert_eq!(num_to_ordinal( 0), "0th");
assert_eq!(num_to_ordinal( 1), "1st");
assert_eq!(num_to_ordinal( 12), "12th");
assert_eq!(num_to_ordinal( 22), "22nd");
assert_eq!(num_to_ordinal( 43), "43rd");
assert_eq!(num_to_ordinal( 67), "67th");
assert_eq!(num_to_ordinal(1901), "1901st");
}
静态分析
确保:
1,总是在在函数尾,格式!
之前初化后缀
.
2,执行函数
时,最多分配一次后缀
.(如果是多次
,编译器会提醒你),
面向表达式
,则如下:
fn num_to_ordinal_expr(x: u32) -> String {
format!("{}{}", x, match (x % 10, x % 100) {
(1, 1) | (1, 21...91) => "st",
(2, 2) | (2, 22...92) => "nd",
(3, 3) | (3, 23...93) => "rd",
_ => "th"
})
}
想要初化
某个状态,然后借用
它时,但仅限于某些控制流分支
.
fn sometimes_initialize(input: i32) {
let string: String; //动态构造串值
let borrowed: &str; //引用串数据
match input {
0...100 => {
//临时构造串...
string = format!("input prints as {}", input);
//...然后从中借用.
borrowed = &string[6..];
}
_ => {
//串字面是*已*借用的引用
borrowed = "期望0 and 100间";
}
}
println!("borrowed: {}", borrowed);
//println!("string: {}", string);
//取消上面注释,会报错.借用已借用了串,你不能再用了.
}
#[test]
fn demo_sometimes_initialize() {
sometimes_initialize(23); //此调用初化"串`"`,
sometimes_initialize(123); //此调用不会
}
有趣在,匹配
后,禁止直接
访问串,因为在访问
前,必须在每个路径
上初化变量
.
但,可用borrowed
访问串中
数据,因为确保已初化了该串
.
编译器确保借用的串数据
不会超过串自身
,且生成代码
确保在串域
尾,如果已初化它,则会释放
它.
总之,为了健壮性,Rust
语言确保在引用
数据前,总是初化它.
匹配
输入可不取
所有权,直接借用
输入;对匹配引用
(如&T
)至关重要.
上面版本的tree_weight
有个很大的缺点:它按值
取输入树
.一旦传递
一棵树给tree_weight_v1
,这棵树
就消失了(如,释放).
#[test]
fn tree_demo_v1_fails() {
let tree = sample_tree();
assert_eq!(tree_weight_v1(tree), (1 + 2 + 3) + 4 + 5);
//assert_eq!(tree_weight_v1(tree), (1 + 2 + 3) + 4 + 5);
//取消注释,会报错.
}
然而,这不是匹配
造成的;而是函数签名
:
fn tree_weight_v1(t: BinaryTree) -> i32 { 0 }
//即此函数拥有了`'t'`的所有权
在Rust
中,匹配不取
所有权,也良好运行
.即,要匹配的输入
是左值式
.
匹配,执行此求值
,然后检查该内存位置
的数据.
(如果输入式
是变量名或字段/指针解引用
,则左值
只是该变量或字段/内存的位置
.如果输入式是生成未命名临时值
的函数调用或其他操作
,则存储在匹配
检查的临时区域(内存位置)
中.)
因此,如果仅想借用一棵树
而不拥有它的tree_weight
版本,则需要利用Rust
匹配的该特性.
fn tree_weight_v2(t: &BinaryTree) -> i32 {
//表示正在*借用*树,&表示借用.
match *t {//解引用
BinaryTree::Leaf(payload) => payload,
BinaryTree::Node(ref left, payload, ref right) => {//引用分支的引用绑定.
tree_weight_v2(left) + payload + tree_weight_v2(right)
}
}
}
#[test]
fn tree_demo_2() {
let tree = sample_tree();
assert_eq!(tree_weight_v2(&tree), (1 + 2 + 3) + 4 + 5);
}
该tree_weight_v2
函数非常像tree_weight_v1
.唯一的区别是:t
是借用
的引用
(用&
),并添加了*t
解引用,重要的是,对Node
的left
和right
使用引用绑定
.
(按左值式)解引用*t
,只是取表示BinaryTree
的内存地址(因为t:&BinaryTree
只是引用
内存中的该数据).
*t
不是复制
树,也不是移动
到新的临时
位置,因为match
按左值
对待它.
引用绑定
首先,非引用
绑定的含义:
匹配T
类型值时,在成功匹配
时,i
标识模式把值从原始
输入移出并移入i
.因此,此时,i
有T
型(或"i:T"
).
对可复制T
(实现Copy
的T
),该模式绑定
表明i变量
拥有T类型值
的所有权.
因此,tree_weight_v2
中负载
的绑定都有i32
类型;i32
类型实现了Copy
,因此把权重
复制到两个分支的负载
中.
而引用绑定
:
匹配T类型
左值时,在成功匹配时,引用绑定(ref i)
,只会借用匹配
数据的引用
.即,成功匹配T
类型值的ref i
表明i借用T
的引用
(即,"i:&T"
).
因此,在tree_weight_v2
的Node
分支中,left
,引用
(包含一棵树的)左边树,而right
则引用右边树
.
在递归调用tree_weight_v2
中,可传递这些引用.
同样,在成功匹配时,可变引用
借用输入
的可变引用:即i:&mut T
.这允许改变
,并确保同时无其他活动
的该数据引用
.
match
的此解构绑定形式
,允许你同时取数据
的不相交部分
的可变引用.
如下递增
给定树中的所有值
.
fn tree_grow(t: &mut BinaryTree) {
//mut':独占权
match *t {
BinaryTree::Leaf(ref mut payload) => *payload += 1,
BinaryTree::Node(ref mut left, ref mut payload, ref mut right) => {
tree_grow(left);//加左
*payload += 1;
tree_grow(right);//加右
}
}
}
#[test]
fn tree_demo_3() {
let mut tree = sample_tree();
tree_grow(&mut tree);
assert_eq!(tree_weight_v2(&tree), (2 + 3 + 4) + 5 + 6);
}
注意,现在通过可变引用
绑定有效负载
;如果不用引用
,则负载
绑定到整数的本地副本
,但想修改
树自身中实际整数
.就需要
用该整数的引用
.
注意,代码可在Node
分支中,可同时绑定左右
.编译器知道这两个值
不是别名,因此允许同时存在两个&mut
引用.
更多:
1,在模式中,如何用Higher
而不是Answer::Higher
,
2,定义新的命名常量
,
3,通过ident @ pattern
绑定
4,
{ let id = expr; ... }
//与如下的区别:
match expr { id => { ... } }