Skip to main content

Rust 列舉與模式配對

 

Rust Enum and Pattern Matching 

Java 中有 enum 這個特殊關鍵字,用來建立一系列同型別的 static instances。
Rust 中也有,也稱為 enum,使用上也類似。
因為 enum 同為 Java static instance 的概念,所以也能運用在 Pattern Matching。

Rust Pattern matching 簡單的比喻就是其他語言的 switch/case 分支結構
因為 Rust enum instance 可以再攜帶獨自型別的資料,因此使用上增加更多變化。

Rust Enum  

Rust enum 基本使用方式  

  • 語法注意: 最後一個 variant 後是以逗號結尾
  • 使用方法類似 Java 須帶類別名稱。
  • enum 本身是一種 struct type,所以也可以攜帶 fields,語法上也未限制 fields 的資料型別。 ** 但特別的是, Rust 允許每個 variants 擁有不同數量/型別的 fields。
//定義區
enum ProcessStatus{
Start(String),
Processing(String, String), //variants 可擁有不同數量/型別的 fields。
End(String), // 這邊是以逗號結尾
}

//使用區
let init_status = ProcessStatus::Start(String::from("開始"));
let exe_status = ProcessStatus::Processing(String::from("進行中", "Processing"));
let done_status = ProcessStatus::End(String::from("完工"));

Rust enum 基本使用方式2(不同型別數量 fields) 

  • 語法注意: 同樣最後一個 variant 後是以逗號結尾

  • enum 本身是一種 struct type,所以也可以攜帶 fields,語法上也未限制 fields 的資料型別。

  • Rust 允許每個 variants 擁有不同數量/型別的 fields。

  • **下方範例加贈取 field 方式** ** Rust enum 允許變體(variants)存在。也就是說每個 enum instance 可以具有不同數量與型別的 fileds。 ** 下面介紹如何取出 enum instance 變體各自的專屬 field 內容。
enum TextFieldDataType {
StringType(String),
DateType(DateTime<Utc>),
NumericType(i32),
ArrayType(i32, i32, i32, i32),
EmptyTYpe(),
}

pub fn fetch_enum_field_value() {

// Style 1: 在 Match Pattern 上便一並宣告變數。
let extract_a =
TextFieldDataType::StringType(String::from("extract by [pattern matching + upacking]"));
match extract_a {
TextFieldDataType::StringType(field) => println!("style A = {}", field),
_ => print!("skip"),
}

// Style 2: 使用 if let 專用語法,同樣在 Match Pattern 中宣告變數。
let extract_b = TextFieldDataType::StringType(String::from("extract by [if let + upacking]"));
let mut field: String = String::from("");
if let TextFieldDataType::StringType(value) = extract_b {
field = value;
}
println!("style B = {}", field);

// Style 3: if let 語法
let extract_c = TextFieldDataType::StringType(String::from("extract by [if let + upacking]2"));
if let TextFieldDataType::StringType(field) = extract_c {
println!("style C: {}", field);
}

// Style 4: Variant 中有多個自訂 fields 時,可以用 解包語法 取出焦點。
let extract_d = TextFieldDataType::ArrayType(10, 20, 30, 40);
let mut second = 0;
if let TextFieldDataType::ArrayType(first, second, _, _) = extract_d {
println!("style D, with ignored : {}", second);
}
////解包時以 _ 來為不重要的變數取一個暫時的名稱佔位符 placeholder。
}

Option 標準函式庫中特殊的 rust enum 

用途:Rust 不應該也不允許出現 Null Pointer Exception 之類的情境。
當有可能出現空值的情境時,在 rust 中選擇以 Option<T> 作為回傳。
Option enum 下有兩個 instance : None 與 Some<T>。 None 可用來借代為空值。

None 與 Some: Option 標準函式庫中特殊的 rust enum

同樣的 Java 中也有 Option 用來避免 Null Pointer Exception,Rust 中的 None 與 Some 也具有相同的設計目的。
Rust enum variants 因為可以擁有不同數量與型別成員變數的特性,因而被選用來時做 Option。

簡單的說便是: Option 這個結構(struct/type)有 Some 與 None 這兩種變體(Variants)。

因此可以利用 Option 加上定義泛型成員的方式來定義有能為空值的變數的型別。
當變數有值時: 以 Some<type> 封裝回傳值
當變數缺值時: 則直接給予 None
注意: Some 的 value 只能取出一次,取出後即歸還記憶體。

Option:取值範例

  • let content = opt.unwrap(); 使用 unwrap 可取出 Option 內容。
fn dummy_dataset_find_by_name(name : String) -> Option<String> {
return Some(String::from("Insect totem"));
}

pub fn option_exercise(){

let totem: Option<String> = dummy_dataset_find_by_name(String::from("key"));

//style A
let content = totem.unwrap();
print!("My name is {}", content);
}

pub fn option_exercise2(){

let totem: Option<String> = dummy_dataset_find_by_name(String::from("key"));

//style B
if let Some(value: String) = totem {
print!("My name is {}", value);
}
}

pub fn option_exercise3(){
let totem: Option<String> = dummy_dataset_find_by_name(String::from("key"));
//style C
let mut certified_name: String = String::from("not found");
if let Some(value) = totem {
certified_name = value;
}
print!("My name is {}", certified_name);
}

Some: use moved value 範例

pub fn option_exercise_error_moved_value(){
// 這個範例會出錯
let totem: Option<String> = dummy_dataset_find_find_by_name(String::from("key"));

if let Some(value: String) = totem {
print!("My name is {}", value);
}
//=> Some 的 value 已經取出

let mut certified_name: String = String::from("not found");
if let Some(value2) = totem { // 這邊會報錯 use of moved value
certified_name = value2;
}
print!("My name is {}", certified_name);
}

Enum and Match Pattern/Switch Case  

Pattern matching 在 Java 中稱為 Switch Case,整體概念相同,使用上有一些小出入。

先說明 Rust 中的 Patterns 限制:

Patterns 可以是 字面量、變數名、enum、萬用字元(佔位符)
分支的模式必須盡舉所有可能性,不然會 Compile Error。
佔位符代替其他所有未列出的 patterns(Java switch-case 中的 default)
(): 單元數值來代表不做任何事情
if let 變體

Pattern matching: enum

enum 可盡舉,故常在 pattern 中使用。

每個 Match Pattern 後應該以逗號做結尾,但若是多行區塊以大括號限制住的話則可省略。

Pattern matching 語法

Pattern matching Syntax

  • => 用來引出後續該執行的 Statement 或 Expression

  • 每個 Matching 後應該以逗號結尾做分隔

  • Patterns 必須盡舉

  • 無法盡舉所有 Patterns 可以以 _ 或 other 來代替剩餘可能性,並以 ()單元數值來代表不做任何事情

  • 下面範例示範: Pattern Matching,當符合指定 Pattern 時執行特定動作。
    match example

enum QuestionType {
DVG(u32),
Multiple(Vec<u32>),
FillIn(String),
}

fn fetch_answer(question: QuestionType) -> String {
match question {
QuestionType::DVG(dvg) => {
return extract_dvg_answer(dvg);
}

QuestionType::Multiple(multi) => {
return extract_multi_answer(multi);
}, //大括號封裝,所以逗號可省略

QuestionType::FillIn(fillin) => {
return extract_fillin_answer(fillin);
}
}
}

fn extract_dvg_answer(dvganswer: u32) -> String {
return dvganswer.clone().to_string();
}

fn extract_fillin_answer(fillin: String) -> String {
return fillin;
}

fn extract_multi_answer(multi: Vec<u32>) -> String {
let mut combine = "".to_owned();
for item in multi {
combine.push_str(&";".to_owned());
combine.push_str(&item.to_string());
}
return combine;
}

pub fn match_exercise() {
let dvg = QuestionType::DVG(10);
let multi = QuestionType::Multiple(vec![1, 3, 5]);
let fillin = QuestionType::FillIn("Hello, I'm Totem.".to_owned());
println!("-----");
println!("DVG answer is : {}", fetch_answer(dvg));
println!("Multiple answer is : {}", fetch_answer(multi));
println!("FillIn answer is : {}", fetch_answer(fillin));

//DVG answer is : 10
//Multiple answer is : ;1;3;5
//FillIn answer is : Hello, I'm Totem.
}
  • 下面範例示範: Pattern Matching 無法盡舉時的處理方式

Pattern Matching 並未限制只能用 enum,其他 數字、literal 等也行。但卻會有盡舉的問題。

解決方式為 catch all:
Catch All 分為兩種情境(兩種 placeholders):

underscore 下畫線 _ : 不在乎傳入參數後續使用時,可以使用 underscore 搭配單元數值 () 使用。
other : 以 other 代替未列舉出的其他任一配動情境。other 可直接想成是傳入參數的臨時變數。實測結果,以其他字眼代替也可達到目的。
注意: 須注意的是因為 Pattern Matching 是依序向下比對,因此 _ 或 other 都需是最後一個 Pattern。

match example: catch all

pub fn catch_all_exercise(){
let choice = "1";

match choice {
"NA" => description(choice),
"ND" => description(choice),
"N" => description(choice),
"R" => description(choice),
"Z" => description(choice),

_plachholder => selected(choice),
}

//Answer: 1
}


fn description(choice: &str){
print!("Force Single: {} ", choice )
}
fn selected(choice: &str){
print!("Answer: {} ", choice )
}