黃岐做網(wǎng)站網(wǎng)站建設(shè)技術(shù)托管
專欄簡介:本專欄作為Rust語言的入門級的文章,目的是為了分享關(guān)于Rust語言的編程技巧和知識。對于Rust語言,雖然歷史沒有C++、和python歷史悠遠,但是它的優(yōu)點可以說是非常的多,既繼承了C++運行速度,還擁有了Java的內(nèi)存管理,就我個人來說,還有一個優(yōu)點就是集成化的編譯工具cargo,語句風(fēng)格和C++極其相似,所以說我本人還是比較喜歡這個語言,特此建立這個專欄,作為學(xué)習(xí)的記錄分享。
日常分享:每天努力一點,不為別的,只是為了日后,能夠多一些選擇,選擇舒心的日子,選擇自己喜歡的人!
前面我們提到過,Rust中沒有了switch case這種模式控制語句,但是喃,除此之外,卻又多了另一種匹配規(guī)則,那就是模式匹配。所以這節(jié)我們就來聊聊模式匹配這種匹配機制。
1、枚舉
枚舉其實在c++中就有過定義,二者相差不大,關(guān)鍵字是enum,枚舉和結(jié)構(gòu)體一樣,也是用來自定義的數(shù)據(jù)類型。
說到枚舉,可能有些同學(xué)還不是特別清楚,枚舉的意義在那里,其實枚舉他只是一個存放字段的一種容器吧,在后面的代碼中,如果你需要多種字段,但是你又不是特別明確具體需要哪些,就把所有可能的字段放在其中,需要什么就使用什么。
enum Error{typeError,lengthError,
}
?例如上面的代碼,定義了一個Error的枚舉類型,這個時候,Error就是一個數(shù)據(jù)類型。
1.1、枚舉值
let oneError = Error::typeError;let twoEroor = Error::lengthError;
?就如上面的代碼,我們定義了兩個實例對象,而他們的數(shù)值則是Error中的兩個字段。
這里注意的是,枚舉的成員位于其標(biāo)識符的命名空間中,并使用兩個冒號分開。
但是有人看到這里就會有疑惑,這里的枚舉類型中的字段都是沒有具體值的,那么我們?nèi)绾螌⒅蹬c枚舉成員關(guān)聯(lián)?
上一節(jié)講解了結(jié)構(gòu)體的概念,這里我們就可以使用結(jié)構(gòu)體來進行綁定:
enum Error{typeError,lengthError,
}struct getError{oneError: Error,twoEroorderError: Error,address:String,
}
let amples = getError{oneError: Error::typeError,twoEroorderError: Error::lengthError,address:String::from("Hello world"),
};
?上面所時代碼就是將枚舉作為結(jié)構(gòu)體的一部分,除了上面的方法,我們似乎還可以使用其他方法,例如將數(shù)據(jù)放進每一個枚舉成員。
enum Error{typeError(String),lengthError(String),
}
let oneError = Error::typeError(String::from("one error"));
let twoEroor = Error::lengthError(String::from("two Eroor error"));
1.2、Option枚舉
前面寫的代碼中,對于枚舉數(shù)據(jù)類型,雖然將值通過結(jié)構(gòu)體進行了綁定,但是卻沒有具體的值,只有通過將值放進枚舉成員,才能獲得值。那么沒有的值又是什么?或者說又有什么作用?
Rust語言和其他語言的一點不同就在于它沒有空值,也就是說不能賦予空值,必須去實現(xiàn)。Rust 并沒有空值,不過它確實擁有一個可以編碼存在或不存在概念的枚舉。
enum Option<T>{
None,
Some(T),
}
?Option<T>
枚舉是如此有用以至于它甚至被包含在了 prelude 之中,你不需要將其顯式引入作用域。另外,它的成員也是如此,可以不需要 Option::
前綴來直接使用 Some
和 None
。這里要注意,Option枚舉是含在標(biāo)準(zhǔn)庫的,不需要我們定義,直接使用,上面知識給出參考。
<T>
語法是一個我們還未講到的 Rust 功能。它是一個泛型類型參數(shù),后面遇到了我們再詳細介紹。
let some_number = Some(5);let some_char = Some('e');let number:Option<i32>=None;
上面的三條語句,變量some_number的類型是i32,some_char的類型是char,而number的類型是i32,只是是一個空值。 對于Option<T>,這里的T可以是任何數(shù)據(jù)類型,除了賦空值外,一般來說不需要注明變量的數(shù)據(jù)類型,除非是特殊需要,Rust可以推斷其變量的數(shù)據(jù)類型。如果是賦空值,就必須注明變量的數(shù)據(jù)類型,否則會報錯。
不過這里需要注意的是,Option<T>標(biāo)注的數(shù)據(jù)類型與相同的數(shù)據(jù)類型變量不能進行運算.
let one_num:Option<i32>=some(20);let s:i32=30;println!("{}",one_num+s);
error[E0425]: cannot find function `some` in this scope
? --> src/main.rs:45:27
?? |
45 |?? let one_num:Option<i32>=some(20);
?? |?????????????????????????? ^^^^ help: a tuple variant with a similar name exists (notice the capitalization): `Some`error[E0369]: cannot add `i32` to `Option<i32>`
? --> src/main.rs:47:24
?? |
47 |?? println!("{}",one_num+s);
?? |???????????????? -------^- i32
?? |???????????????? |
?? |???????????????? Option<i32>Some errors have detailed explanations: E0369, E0425.
For more information about an error, try `rustc --explain E0369`.
error: could not compile `number` due to 2 previous errors
當(dāng)運行上述代碼的時候就會出現(xiàn)這種報錯,這是為什么喃?這是由于當(dāng)我們使用Option<T> 數(shù)據(jù)類型的時候就表明該數(shù)據(jù)可能為空,而我們使用i32(或其他數(shù)據(jù)類型)的時候,就已經(jīng)表明改變兩不可能為空值,所以才會出現(xiàn)報錯,根本原因還是在與其數(shù)據(jù)類型被系統(tǒng)判定為兩種數(shù)據(jù)類型.
換句話說,在對 Option<T>
進行運算之前必須將其轉(zhuǎn)換為 T
。通常這能幫助我們捕獲到空值最常見的問題之一:假設(shè)某值不為空但實際上為空的情況。
消除了錯誤地假設(shè)一個非空值的風(fēng)險,會讓你對代碼更加有信心。為了擁有一個可能為空的值,你必須要顯式的將其放入對應(yīng)類型的 Option<T>
中。接著,當(dāng)使用這個值時,必須明確的處理值為空的情況。只要一個值不是 Option<T>
類型,你就 可以 安全的認定它的值不為空。這是 Rust 的一個經(jīng)過深思熟慮的設(shè)計決策,來限制空值的泛濫以增加 Rust 代碼的安全性。
那么當(dāng)有一個 Option<T>
的值時,如何從 Some
成員中取出 T
的值來使用它呢?Option<T>
枚舉擁有大量用于各種情況的方法:你可以查看它的文檔。熟悉 Option<T>
的方法將對你的 Rust 之旅非常有用。
總的來說,為了使用 Option<T>
值,需要編寫處理每個成員的代碼。你想要一些代碼只當(dāng)擁有 Some(T)
值時運行,允許這些代碼使用其中的 T
。也希望一些代碼只在值為 None
時運行,這些代碼并沒有一個可用的 T
值。match
表達式就是這么一個處理枚舉的控制流結(jié)構(gòu):它會根據(jù)枚舉的成員運行不同的代碼,這些代碼可以使用匹配到的值中的數(shù)據(jù)。
2、match控制流結(jié)構(gòu)
我們前面說了在Rust語言中沒有switch這種控制流語句,但是它卻推出了match這種強大的控制流運算符。在python語言中,match是正則表達式中的匹配函數(shù),所以這里也可以理解為匹配函數(shù)。
先來看個例子:
enum error_message{E0425,E0369,E2345,
}
fn get_error_message(message:error_message)->u32{match message{error_message::E0425 =>{println!("cannot find function `some` in this scope");return 0;}error_message::E0369=>{println!("cannot add `i32` to `Option<i32>");return 1;}error_message::E2345=>{println!("could not compile `number` due to 2 previous errors");return 2;}}
}
fn main(){let mut error=get_error_message(error_message::E2345);println!("{}",error);get_error_message(error_message::E0425);
}
就如上面的代碼,先是定義了一個枚舉數(shù)據(jù)類型,然后定義了一個函數(shù),在函數(shù)中使用了match控制流。根據(jù)不同的參數(shù)值,返回不同的值,并打印出結(jié)果。
1.1、綁定值模式
匹配分支的另一個有用的功能是可以綁定匹配的模式的部分值。這也就是如何從枚舉成員中提取值的。
/*
enum error_message{E0425,E0369,E2345,
}
fn get_error_message(message:error_message)->u32{match message{error_message::E0425 =>{println!("cannot find function `some` in this scope");return 0;}error_message::E0369=>{println!("cannot add `i32` to `Option<i32>");return 1;}error_message::E2345=>{println!("could not compile `number` due to 2 previous errors");return 2;}}
}
fn main(){let mut error=get_error_message(error_message::E2345);println!("{}",error);get_error_message(error_message::E0425);
}
*/
#[derive(Debug)]
enum UsState{Alabama,Alaska,
}
enum Coin{Penny,Nickel,Dime,Quarter(UsState),
}
fn value_cents(coin:Coin) -> usize {match coin{Coin::Penny =>{return 1;}Coin::Nickel =>{return 5;}Coin::Dime =>{return 10;}Coin::Quarter(state)=>{println!("State quarter from {:#?}",state);return 25;}}
}
fn main()
{let b=UsState::Alaska;let c=Coin::Quarter(b);value_cents(c);}
?如果調(diào)用 value_in_cents(Coin::Quarter(UsState::Alaska))
,coin
將是 Coin::Quarter(UsState::Alaska)
。當(dāng)將值與每個分支相比較時,沒有分支會匹配,直到遇到 Coin::Quarter(state)
。這時,state
綁定的將會是值 UsState::Alaska
。接著就可以在 println!
表達式中使用這個綁定了,像這樣就可以獲取 Coin
枚舉的 Quarter
成員中內(nèi)部的州的值。
1.2、匹配Option<T>
我們在之前的部分中使用 Option<T>
時,是為了從 Some
中取出其內(nèi)部的 T
值;我們還可以像處理 Coin
枚舉那樣使用 match
處理 Option<T>
!只不過這回比較的不再是硬幣,而是 Option<T>
的成員,但 match
表達式的工作方式保持不變。
例如:
fn plus_amount(amount:Option<i32>)->Option<i32>
{match amount {None=>{println!("該值為空值");return None;}Some(i)=>{println!("該值不是空值,值為{}",i);return Some(i);}}
}
fn main(){let five=Some(5);let num1=plus_amount(five);plus_amount(None);
}
1.3、通配模式和_占位符
其實除了枚舉,match控制流也可以用于其他形式,比如:
let num=30;match num{10=>{println!("10");}11 => println!("11"),other => println!("other"),}
?上面的代碼中,我們在最后使用了other這個變量,這個變量覆蓋了所有其他的可能值,除了我們列出來的可能性,other會包含所有的其他可能性,所以other一定要放在最后,否無法達到目的。
不過在這里因該有人發(fā)現(xiàn)了,other會綁定match匹配的值,這個我們將上面的程序更改一下,就能的出這個結(jié)論:
fn main(){let five=Some(5);let num1=plus_amount(five);plus_amount(None);let num=30;match num{10=>{println!("10");}11 => println!("11"),other => println!("other的值為:{}",other),}
}
?other的值為:30
上面就是輸出結(jié)果,這說明other綁定到了match匹配的值上,這樣做的好處就是可以獲得匹配值,將其進行使用,但是如果我們不需要使用那個值,這樣做就有點浪費,所以Rust也推出了_占位符,占位符只是表示可以匹配任意值而不能綁定到該值。
fn main(){let five=Some(5);let num1=plus_amount(five);plus_amount(None);let num=30;match num{10=>{println!("10");}11 => println!("11"),_ => println!("other"),}
}
?3、if let間接控制流
Rust中的if let控制流說的簡單點,就相當(dāng)于c++中的if else語句,對于一些簡單的判別,使用if let控制流語句將會簡單很多。例如:
fn main()
{let config=Some(3u8);match config{Some(max)=>println!("{}",max),_=>(),}
}
上面面代碼的意思是,匹配config的值,如果值是Some,就將值綁定到max變量上,然后輸出,否則就忽略。
除了上面這樣的方式,我們還可以使用其他的方式:
fn main()
{let config=Some(3u8);if let Some(max)=config{println!("{}",max);}
}
這樣看來是不是就簡單的多了,所以說從某種角度來看,if let語句確實簡單了很多。
#[derive(Debug)]
enum UsState{Alabama,Alaska,
}
enum Coin{Penny,Nickel,Dime,Quarter(UsState),
}
fn main()
{let mut count=0;let b=UsState::Alaska;let coin=Coin::Quarter(b);//以下兩種方式都可以/*match coin{Coin::Quarter(state)=>{println!("State quarter from {:#?}",state);}_=>count+=1,}*/if let Coin::Quarter(state) = coin{println!("State quarter from {:#?}",state);}else{count+=1;}
}
4、總結(jié)
現(xiàn)在我們涉及到了如何使用枚舉來創(chuàng)建有一系列可列舉值的自定義類型。我們也展示了標(biāo)準(zhǔn)庫的 Option<T>
類型是如何幫助你利用類型系統(tǒng)來避免出錯的。當(dāng)枚舉值包含數(shù)據(jù)時,你可以根據(jù)需要處理多少情況來選擇使用 match
或 if let
來獲取并使用這些值。
你的 Rust 程序現(xiàn)在能夠使用結(jié)構(gòu)體和枚舉在自己的作用域內(nèi)表現(xiàn)其內(nèi)容了。在你的 API 中使用自定義類型保證了類型安全:編譯器會確保你的函數(shù)只會得到它期望的類型的值。
下一節(jié)我們學(xué)習(xí)模塊系統(tǒng)。