pattern 是 rust 的特殊語法,用來匹配類別的 struct,結合 match
expression 及其他 struct 可提供更多程式 control flow 的支配權。Pattern 由以下內容組成
- Literals
- Destructured arrays, enums, structs, or tuples
- Variables
- Wildcards
- Placeholders
這些元件就是要處理的 data,接著可用匹配值決定程式是否有正確的資料,運作特定部分的 code
透過一些值與 pattern 比較來使用它們,如果匹配,就做對應處理。
以下討論 pattern 適用處,refutable 與 irrefutable 模式的區別,及不同類別的 pattern 語法
所有可能用到 Pattern 的地方
match
Arms
match
表達式由 match
關鍵字、用於匹配的值和一個或多個分支構成,這些分支包含一個模式和在值匹配分支的模式時運行的表達式:
match VALUE {
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
}
match
表達式必須是 窮盡(exhaustive)的,所有可能的值都必須被考慮到。
有一個特定的模式 _
可以匹配所有情況,不過它從不綁定任何變數。這在例如希望忽略任何未指定值的情況很有用。本章之後的 “在模式中忽略值” 部分會詳細介紹 _
模式的更多細節。
Conditional if let
Expression
chap6 討論過 if let
及如何撰寫只關心一個狀況的match
語法的簡寫。if let
可對應一個 optional 帶有 code 的 else,在 if let
pattern 不匹配時運作。
可以組合併匹配 if let
、else if
和 else if let
表達式。相比 match
表達式一次只能將一個值與模式比較提供了更多靈活性;一系列 if let
、else if
、else if let
分支並不要求其條件相互關聯。
fn main() {
let favorite_color: Option<&str> = None;
let is_tuesday = false;
let age: Result<u8, _> = "34".parse();
if let Some(color) = favorite_color {
println!("Using your favorite color, {}, as the background", color);
} else if is_tuesday {
println!("Tuesday is green day!");
} else if let Ok(age) = age {
if age > 30 {
println!("Using purple as the background color");
} else {
println!("Using orange as the background color");
}
} else {
println!("Using blue as the background color");
}
}
注意 if let
也可以像 match
分支那樣引入 shadowed 變數:if let Ok(age) = age
引入了一個新的 shadowed 變數 age
,它包含 Ok
成員中的值。這意味著 if age > 30
條件需要位於這個代碼塊內部;不能將兩個條件組合為 if let Ok(age) = age && age > 30
,因為我們希望與 30 進行比較的被覆蓋的 age
直到大括號開始的新作用域才是有效的。
if let
表達式的缺點在於無法用 compiler 檢查窮盡性,而 match
表達式有做檢查。如果去掉最後的 else
塊而遺漏處理一些情況,編譯器也不會警告這類可能的邏輯錯誤。
while let
Conditional Loop
只要 stack.pop()
回傳 Some
就列印
let mut stack = Vec::new();
stack.push(1);
stack.push(2);
stack.push(3);
while let Some(top) = stack.pop() {
println!("{}", top);
}
for
Loop
for
可以獲取一個模式,例如在 for
循環中使用模式來解構元組
用 enumerate
方法產生一個迭代器,得到一個值和其在迭代器中的索引,他們位於一個元組中。第一個 enumerate
呼叫會產生元組 (0, 'a')
。當這個值匹配模式 (index, value)
,index
將會是 0 而 value
將會是 'a'
,並印出第一行輸出。
let v = vec!['a', 'b', 'c'];
for (index, value) in v.iter().enumerate() {
println!("{} is at index {}", value, index);
}
let
statement
let x = 5;
其實就是 pattern,也就是 let PATTERN = EXPRESSION;
這個模式實際上等於 “將任何值綁定到變數 x
,不管值是什麼”
// 將一個 tuple 與模式匹配
let (x, y, z) = (1, 2, 3);
function parameters
函數參數也可以是模式,x
部分就是一個模式
fn foo(x: i32) {
// 代碼
}
ex:
fn print_coordinates(&(x, y): &(i32, i32)) {
println!("Current location: ({}, {})", x, y);
}
fn main() {
let point = (3, 5);
print_coordinates(&point);
}
closure 類似 function,故也可以在 closure parameter 使用 pattern
在一些地方,模式必須是 irrefutable 的,必須匹配所提供的任何值。在另一些情況,他們則可以是 refutable 的。
Refutability: 是否 pattern 會匹配失敗
模式有兩種形式:refutable(可反駁的)和 irrefutable(不可反駁的)。能匹配任何傳遞的可能值的模式被稱為是 不可反駁的(irrefutable)。例如 let x = 5;
語句中的 x
,因為 x
可以匹配任何值所以不可能會失敗。
對某些可能的值進行匹配會失敗的模式被稱為是 可反駁的(refutable)。一個這樣的例子便是 if let Some(x) = a_value
表達式中的 Some(x)
;如果變量 a_value
中的值是 None
而不是 Some
,那麼 Some(x)
模式不能匹配。
let
語句、 函數參數和 for
循環只能接受不可反駁的模式,因為通過不匹配的值程序無法進行有意義的工作。if let
和 while let
表達式被限制為只能接受可反駁的模式,因為根據定義就是要處理可能的失敗:條件表達式的功能就是根據成功或失敗執行不同的操作。
通常無需擔心可反駁和不可反駁模式的區別,不過確實需要熟悉可反駁性的概念,這樣當在錯誤信息中看到時就知道如何應對。遇到這些情況,根據代碼行為的意圖,需要修改模式或者使用模式的結構。
一個嘗試在 Rust 要求不可反駁模式的地方使用可反駁模式以及相反情況的例子
let Some(x) = some_option_value;
會發生編譯錯誤
error[E0005]: refutable pattern in local binding: `None` not covered
-->
|
3 | let Some(x) = some_option_value;
| ^^^^^^^ pattern `None` not covered
為了修復在需要不可反駁模式的地方使用可反駁模式的情況,可以修改使用模式的代碼:不同於使用 let
,可以使用 if let
。
if let Some(x) = some_option_value {
println!("{}", x);
}
如果為 if let
提供了一個總是會匹配的模式,嘗試把不可反駁模式用到 if let
上,比如以下的 x
,則會出錯:
if let x = 5 {
println!("{}", x);
};
會發生編譯錯誤
error[E0162]: irrefutable if-let pattern
--> <anon>:2:8
|
2 | if let x = 5 {
| ^ irrefutable pattern
匹配分支必須使用可反駁模式,除了最後一個分支需要使用能匹配任何剩餘值的不可反駁模式。
Pattern Syntax
以下列出所有 pattern 的有效語法
matching literals
let x = 1;
match x {
1 => println!("one"),
2 => println!("two"),
3 => println!("three"),
_ => println!("anything"),
}
matching named variables
Named variables 是 irrefutable(不可反駁的)可 match any value
match
會開始一個新作用域,match
表達式中作為模式的一部分聲明的變數會覆蓋 match
結構之外的同名變數,與所有變數一樣。
fn main() {
let x = Some(5);
let y = 10;
match x {
Some(50) => println!("Got 50"),
// y 是 shaowed variable,跟上面的 y 無關,可匹配任何 Some 裡面的 value
// 當 scope 結束,y 的作用域就結束
Some(y) => println!("Matched, y = {:?}", y),
_ => println!("Default case, x = {:?}", x),
}
// 這裡的 y 還是一樣是 10
println!("at the end: x = {:?}, y = {:?}", x, y);
}
multiple patterns
在 match
表達式中,可以使用 |
語法匹配多個模式,它代表 or 的意思。
let x = 1;
match x {
// 符合 1 或 2
1 | 2 => println!("one or two"),
3 => println!("three"),
_ => println!("anything"),
}
matching rangers of values with …
...
語法允許你匹配一個閉區間範圍內的值。
let x = 5;
match x {
// 如果 x 是 1、2、3、4 或 5,就會匹配
1 ... 5 => println!("one through five"),
_ => println!("something else"),
}
rangers 範圍只允許用於數字或 char
值
let x = 'c';
match x {
'a' ... 'j' => println!("early ASCII letter"),
'k' ... 'z' => println!("late ASCII letter"),
_ => println!("something else"),
}
destructing to break apart values
可以使用模式來解構 structs、enums、tuples 和引用,以便使用這些值的不同部分。
destructing structs
可以通過帶有模式的
let
語句將其分解:
struct Point { x: i32, y: i32, } fn main() { let p = Point { x: 0, y: 7 }; let Point { x: a, y: b } = p; assert_eq!(0, a); assert_eq!(7, b); }
這個例子展示了模式中的變數名不必與結構體中的欄位一致。不過通常希望變數名與欄位名一致以便於理解變數來自於哪些欄位。
因為變數名匹配欄位名是常見的,同時因為
let Point { x: x, y: y } = p;
包含了很多重複,所以對於匹配欄位的模式存在簡寫:只需列出結構體欄位的名稱,則模式產生的變數會有相同的名稱。
struct Point { x: i32, y: i32, } fn main() { let p = Point { x: 0, y: 7 }; let Point { x, y } = p; assert_eq!(0, x); assert_eq!(7, y); }
也可以在部分struct 模式中使用 literal 進行解析,而不是為所有的欄位產生變數。
fn main() { let p = Point { x: 0, y: 7 }; match p { // x 軸 Point { x, y: 0 } => println!("On the x axis at {}", x), // y 軸 Point { x: 0, y } => println!("On the y axis at {}", y), Point { x, y } => println!("On neither axis: ({}, {})", x, y), } }
destructing enums
enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32), } fn main() { let msg = Message::ChangeColor(0, 160, 255); match msg { // Message::Quit 這樣沒有任何資料的enum,不能進一步解構其值。只能匹配其 literal Message::Quit,因此模式中沒有任何變數 Message::Quit => { println!("The Quit variant has no data to destructure.") }, // 使用大括號並列出欄位變數,以便將其分解以供此分支的代碼使用 Message::Move { x, y } => { println!( "Move in the x direction {} and in the y direction {}", x, y ); } Message::Write(text) => println!("Text message: {}", text), Message::ChangeColor(r, g, b) => { println!( "Change the color to red {}, green {}, and blue {}", r, g, b ) } } }
destructing reference
當模式所匹配的值中包含引用時,需要解構引用之中的值,這可以通過在模式中指定
&
做到。這讓我們得到一個包含引用所指向資料的變數,而不是包含引用的變數。這個技術在通過迭代器遍歷引用時,要使用 closure 中的值而不是其引用時非常有用。
遍歷一個 vector 中的
Point
實例的引用,並同時解構引用和其中的 struct 以方便對x
和y
值進行計算
let 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();
destructing nested structs and enums
以匹配嵌套的 enum
enum Color { Rgb(i32, i32, i32), Hsv(i32, i32, i32) } enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(Color), } fn main() { let msg = Message::ChangeColor(Color::Hsv(0, 160, 255)); match msg { Message::ChangeColor(Color::Rgb(r, g, b)) => { println!( "Change the color to red {}, green {}, and blue {}", r, g, b ) }, Message::ChangeColor(Color::Hsv(h, s, v)) => { println!( "Change the color to hue {}, saturation {}, and value {}", h, s, v ) } _ => () } }
destructing nested structs and tuples
struct 和 tuple 嵌套在 tuple 中
let ((feet, inches), Point {x, y}) = ((3, 10), Point { x: 3, y: -10 });
Ignoring values in a pattern
有時忽略模式中的一些值是有用的,例如 match
中最後捕獲全部情況的分支實際上沒有做任何事,但是它確實對所有剩餘情況負責。有一些簡單的方法可以忽略模式中全部或部分值:使用 _
模式,使用一個以下劃線開始的名稱,或者使用 ..
忽略所剩部分的值。
ignoring an entire value with
_
_
也可以用在函數的參數上
fn foo(_: i32, y: i32) { println!("This code only uses the y parameter: {}", y); } fn main() { foo(3, 4); }
ignoring parts of a value with a nested
_
只需要測試部分值但在期望運行的代碼部分中沒有使用它們時,也可以在另一個模式內部使用
_
來只忽略部分值。
let mut setting_value = Some(5); let new_setting_value = Some(10); match (setting_value, new_setting_value) { // 不需要 Some 中的值時 (Some(_), Some(_)) => { println!("Can't overwrite an existing customized value"); } _ => { setting_value = new_setting_value; } }
也可以在一個模式中的多處使用 underline 來忽略特定值
let numbers = (2, 4, 8, 16, 32); match numbers { (first, _, third, _, fifth) => { println!("Some numbers: {}, {}, {}", first, third, fifth) }, }
ignoring an unused variable by starting its name with
_
產生了一個變量卻不在任何地方使用它,Rust 會給你一個警告,因為這可能會是個 bug。但是有時是有用的,例如你正在設計原型或剛剛開始一個 project。這時你希望告訴 Rust 不要警告未使用的變量,為此可以用 underline 作為變數名的開頭。
fn main() { let _x = 5; let y = 10; }
只使用
_
和使用以下劃線開頭的名稱有些微妙的不同:比如_x
仍會將值綁定到變數,而_
則完全不會綁定。以下會得到一個編譯錯誤,因為s
的值仍然會移動進_s
,並阻止我們再次使用s
。
let s = Some(String::from("Hello!")); if let Some(_s) = s { println!("found a string"); } println!("{:?}", s);
要改寫為
let s = Some(String::from("Hello!")); if let Some(_) = s { println!("found a string"); } println!("{:?}", s);
ignoring remaining parts of a value with
..
對於有多個部分的值,可以使用
..
語法來只使用部分並忽略其它值,同時避免不得不對每一個忽略值列出下劃線。..
模式會忽略模式中剩餘的任何沒有顯式匹配的值部分。以下有一個Point
存放了三維空間中的坐標。在match
表達式中,我們希望只操作x
座標並忽略y
和z
的值:
struct Point { x: i32, y: i32, z: i32, } let origin = Point { x: 0, y: 0, z: 0 }; match origin { // 忽略 Point 中除 x 以外的 fields Point { x, .. } => println!("x is {}", x), }
fn main() { let numbers = (2, 4, 8, 16, 32); match numbers { // 只匹配 tuple 中的第一個和最後一個值並忽略掉所有其它值 (first, .., last) => { println!("Some numbers: {}, {}", first, last); }, } }
如果期望匹配和忽略的值是不明確的,Rust 會給編譯錯誤。
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
(.., second, ..) => {
println!("Some numbers: {}", second)
},
}
}
Extra Conditionals with Match Guards
匹配守衛(match guard)是一個指定與 match
分支模式之後的額外 if
條件,它也必須被滿足才能選擇此分支。匹配守衛用於表達比單獨的模式所能允許的更為複雜的情況。
let num = Some(4);
match num {
// 判斷 x 是否 < 5
Some(x) if x < 5 => println!("less than five: {}", x),
Some(x) => println!("{}", x),
None => (),
}
可以使用match guard來解決模式中 shadowed 變數的問題,那裡 match
表達式的模式中產生了一個變數而不是使用 match
之外的同名變數。新變數就代表不能夠測試外部變數的值。可使用匹配守衛修復這個問題:
fn main() {
let x = Some(5);
let y = 10;
match x {
Some(50) => println!("Got 50"),
// 用 matching guard 測試跟外部變數是否相等
// n == y 沒有產生新變數,這裡的 y 就是外部的 y
Some(n) if n == y => println!("Matched, n = {:?}", n),
_ => println!("Default case, x = {:?}", x),
}
println!("at the end: x = {:?}, y = {:?}", x, y);
}
let x = 4;
let y = false;
match x {
// 也可以使用 或 運算符 | 來指定多個模式,同時 match guard 的條件會作用域所有的模式
4 | 5 | 6 if y => println!("yes"),
_ => println!("no"),
}
@ Bindings
at (@
)讓我們在產生一個存放值的變數的同時測試其值是否匹配模式。
enum Message {
Hello { id: i32 },
}
let msg = Message::Hello { id: 5 };
match msg {
// 測試 Message::Hello 的 id 字段是否位於 3...7 範圍內,同時也希望能綁定其值到 id_variable 中以便此分支相關聯的 code 可以使用它
Message::Hello { id: id_variable @ 3...7 } => {
println!("Found an id in range: {}", id_variable)
},
// 指定了一個範圍 10, 11, 12,但裡面不能使用 id,因為沒有將 id 儲存到另一個變數中
Message::Hello { id: 10...12 } => {
println!("Found an id in another range")
},
// 沒有範圍的變數
Message::Hello { id } => {
println!("Found some other id: {}", id)
},
}
沒有留言:
張貼留言