楔子
關(guān)于 Rust 的基礎(chǔ)知識我們已經(jīng)介紹一部分了,下面來做一個(gè)總結(jié)。因?yàn)?Rust 是一門難度非常高的語言,在學(xué)習(xí)完每一個(gè)階段之后,對學(xué)過的內(nèi)容適當(dāng)總結(jié)一下是很有必要的。
那么下面就開始吧,將以前說過的內(nèi)容再總結(jié)一遍,并且在這個(gè)過程中還會補(bǔ)充一些之前遺漏的內(nèi)容。
原生類型
首先是 Rust 的原生類型,原生類型包含標(biāo)量類型和復(fù)合類型。
另外在 Rust 里面,空元組也被稱為單元類型。
在聲明變量的時(shí)候,可以顯式地指定類型,舉個(gè)例子:
fn?main(){ ????let?x:?i64?=?123; ????let?y:?bool?=?true; ????let?z:?[u8;?3]?=?[1,?2,?3]; ????println!("x?=?{}",?x); ????println!("y?=?{}",?y); ????println!("z?=?{:?}",?z); ????/* ????x?=?123 ????y?=?true ????z?=?[1,?2,?3] ????*/ }
另外數(shù)字比較特殊,還可以通過后綴指定類型。
fn?main(){ ????//?u8?類型 ????let?x?=?123u8; ????//?f64?類型 ????let?y?=?3.14f64; ????println!("x?=?{}",?x); ????println!("y?=?{}",?y); ????/* ????x?=?123 ????y?=?3.14 ????*/ }
如果沒有顯式指定類型,也沒有后綴,那么整數(shù)默認(rèn)為 i32,浮點(diǎn)數(shù)默認(rèn)為 f64。
fn?main(){
????//?整數(shù)默認(rèn)為?i32 ????let?x?=?123; ????//?浮點(diǎn)數(shù)默認(rèn)為?f64 ????let?y?=?3.14; }
最后 Rust 還有一個(gè)自動推斷功能,會結(jié)合上下文推斷數(shù)值的類型。
fn?main(){
????//?本來默認(rèn)?x?為?i32,y?為?f64 ????let?x?=?123; ????let?y?=?3.14; ????//?但是這里我們將?x,?y?組合成元組賦值給了?t ????//?而?t?是?(u8,?f32),所以?Rust?會結(jié)合上下文 ????//?將?x?推斷成?u8,將?y?推斷成?f32 ????let?t:?(u8,?f32)?=?(x,?y); }
但如果我們在創(chuàng)建 x 和 y 的時(shí)候顯式地規(guī)定了類型,比如將 x 聲明為 u16,那么代碼就不合法了。因?yàn)?t 的第一個(gè)元素是 u8,但傳遞的 x 卻是 u16,此時(shí)就會報(bào)錯(cuò),舉個(gè)例子:
Rust 對類型的要求非常嚴(yán)格,即便都是數(shù)值,類型不同也不能混用。那么這段代碼應(yīng)該怎么改呢?
fn?main(){
????let?x?=?123u16; ????let?y?=?3.14; ????let?t:?(u8,?f32)?=?(x?as?u8,?y); }
通過 as 關(guān)鍵字,將 x 轉(zhuǎn)成 u8 就沒問題了。
然后我們上面創(chuàng)建的整數(shù)都是十進(jìn)制,如果在整數(shù)前面加上 0x, 0o, 0b,還可以創(chuàng)建十六進(jìn)制、八進(jìn)制、二進(jìn)制的整數(shù)。并且在數(shù)字比較多的時(shí)候,為了增加可讀性,還可以使用下劃線進(jìn)行分隔。
fn?main(){
????let?x?=?0xFF; ????let?y?=?0o77; ????//?數(shù)字較多時(shí),使用下劃線分隔 ????let?z?=?0b1111_1011; ????//?以?4?個(gè)數(shù)字為一組,這樣最符合人類閱讀 ????//?但?Rust?語法則沒有此要求,我們可以加上任意數(shù)量的下劃線 ????let?z?=?0b1_1_1_1_______10______1_1; ????println!("x?=?{},?y?=?{},?z?=?{}",?x,?y,?z); ????/* ????x?=?255,?y?=?63,?z?=?251 ????*/ }
至于算術(shù)運(yùn)算、位運(yùn)算等操作,和其它語言都是類似的,這里不再贅述。
元組
再來單獨(dú)看看元組,元組是一個(gè)可以包含各種類型值的組合,使用括號來創(chuàng)建,比如?(T1, T2, ...),其中 T1、T2 是每個(gè)元素的類型。函數(shù)可以使用元組來返回多個(gè)值,因?yàn)樵M可以擁有任意多個(gè)值。
Python 的多返回值,本質(zhì)上也是返回了一個(gè)元組。
fn?main(){
????//?t?的類型就是?(i32,?f64,?u8,?f32) ????let?t?=?(12,?3.14,?33u8,?2.71f32); ????//?當(dāng)然你也可以這么做 ????let?t:?(i32,?f64,?u8,?f32)?=?(12,?3.14,?33,?2.71); ????//?但下面的做法是非法的 ????//?因?yàn)?t?的第一個(gè)元素要求是?i32,而我們傳遞的?u8 ????/* ????let?t:?(i32,?f64)?=?(12u8,?3.14) ????*/ ????//?應(yīng)該改成這樣 ????/* ????let?t:?(i32,?f64)?=?(12i32,?3.14) ????*/ ????//?只不過這種做法有點(diǎn)多余,因?yàn)?t?已經(jīng)規(guī)定好類型了 ????//?所以沒必要寫成?12i32,直接寫成?12?就好 }
元組里面的元素個(gè)數(shù)是固定的,類型也是固定的,但是每個(gè)元素之間可以是不同的類型。
fn?main(){
????//?此時(shí)?t?的類型就會被推斷為 ????//?((i32,?f64,?i32),?(i32,?u16),?i32) ????let?t?=?((1,?2.71,?3),?(1,?2u16),?33); }
然后是元組的打印,有兩種方式。
fn?main(){
????let?t?=?(1,?22,?333); ????//?元組打印需要使用?"{:?}" ????println!("{:?}",?t); ????//?或者使用?"{:#?}"?美化打印 ????println!("{:#?}",?t); ????/* ????(1,?22,?333) ????( ????????1, ????????22, ????????333, ????) ????*/ }
有了元組之后,還可以對其進(jìn)行解構(gòu)。
fn?main()?{
????let?t?=?(1,?3.14,?7u16); ????//?將?t?里面的元素分別賦值給?x、y、z ????//?這個(gè)過程稱為元組的結(jié)構(gòu) ????//?變量多元賦值也是通過這種方式實(shí)現(xiàn)的 ????let?(x,?y,?z)?=?t; ????println!("x?=?{},?y?=?{},?z?=?{}",?x,?y,?z); ????//?x?=?1,?y?=?3.14,?z?=?7 ????//?當(dāng)然我們也可以通過索引,單獨(dú)獲取元組的某個(gè)元素 ????//?只不過方式是?t.索引,而不是?t[索引] ????let?x?=?t.0; }
再補(bǔ)充一點(diǎn),創(chuàng)建元組的時(shí)候使用的是小括號,但我們知道小括號也可以起到一個(gè)限定優(yōu)先級的作用。因此當(dāng)元組只有一個(gè)元素的時(shí)候,要顯式地在第一個(gè)元素后面加上一個(gè)逗號。
fn?main()?{
????//?t1?是一個(gè)?i32,因?yàn)?(1)?等價(jià)于?1 ????let?t1?=?(1); ????//?t2?才是元組,此時(shí)?t2?是?(i32,)?類型 ????let?t2?=?(1,); ????println!("t1?=?{}",?t1); ????println!("t2?=?{:?}",?t2); ????/* ????t1?=?1 ????t2?=?(1,) ????*/ ????//?同樣的,當(dāng)指定類型的時(shí)候也是如此 ????//?如果寫成?let?t3:?(i32),則等價(jià)于?let?t3:?i32 ????let?t3:?(i32,)?=?(1,); }
至于將元組作為函數(shù)參數(shù)和返回值,也是同樣的用法,這里就不贅述了。
數(shù)組和切片
數(shù)組(array)是一組擁有相同類型 T 的對象的集合,在內(nèi)存中是連續(xù)存儲的,所以數(shù)組不僅要求長度固定,每個(gè)元素類型也必須一樣。數(shù)組使用中括號來創(chuàng)建,且它們的大小在編譯時(shí)會被確定。
fn?main()?{
????//?數(shù)組的類型被標(biāo)記為?[T;?length] ????//?其中?T?為元素類型,length?為數(shù)組長度 ????let?arr:?[u8;?5]?=?[1,?2,?3,?4,?5]; ????println!("{:?}",?arr); ????/* ????[1,?2,?3,?4,?5] ????*/ ????//?不指定類型,可以自動推斷出來 ????//?此時(shí)會被推斷為?[i32;?5] ????let?arr?=?[1,?2,?3,?4,?5]; ????//?Rust?數(shù)組的長度也是類型的一部分 ????//?所以下面的?arr1?和?arr2?是不同的類型 ????let?arr1?=?[1,?2,?3];??//?[i32;?3]?類型 ????let?arr2?=?[1,?2,?3,?4];??//?[i32;?4]?類型 ????//?所以?let?arr1:?[i32;?4]?=?[1,?2,?3]?是不合法的 ????//?因?yàn)槁暶鞯念愋褪?[i32;?4],但傳遞的值的類型是?[i32;?3] }
如果創(chuàng)建的數(shù)組所包含的元素都是相同的,那么有一種簡便的創(chuàng)建方式。
fn?main()?{
????//?有?5?個(gè)元素,且元素全部為?3 ????let?arr?=?[3;?5]; ????println!("{:?}",?arr); ????/* ????[3,?3,?3,?3,?3] ????*/ }
然后是元素訪問,這個(gè)和其它語言一樣,也是基于索引。
fn?main()?{
????let?arr?=?[1,?2,?3]; ????println!("arr[1]?=?{}",?arr[1]); ????/* ????arr[1]?=?2 ????*/ ????//?如果想修改數(shù)組的元素,那么數(shù)組必須可變 ????//?無論是將新的數(shù)組賦值給?arr ????//?還是通過?arr?修改當(dāng)前數(shù)組內(nèi)部的值 ????//?都要求數(shù)組可變,其它數(shù)據(jù)結(jié)構(gòu)也是如此 ????let?mut?arr?=?[1,?2,?3]; ????//?修改當(dāng)前數(shù)組的元素,要求?arr?可變 ????arr[1]?=?222; ????println!("arr?=?{:?}",?arr); ????/* ????arr?=?[1,?222,?3] ????*/ ????//?將一個(gè)新的數(shù)組綁定在?arr?上 ????//?也要求?arr?可變 ????arr?=?[2,?3,?4]; ????println!("arr?=?{:?}",?arr); ????/* ????arr?=?[2,?3,?4] ????*/ }
說完了數(shù)組,再來說一說切片(slice)。切片允許我們對數(shù)組的某一段區(qū)間進(jìn)行引用,而無需引用整個(gè)數(shù)組。
fn?main()?{
????let?arr?=?[1,?2,?3,?4,?5,?6]; ????let?slice?=?&arr[2..5]; ????println!("{:?}",?slice);??//?[3,?4,?5] ????println!("{}",?slice[1]);??//?4 }
我們來畫一張圖描述一下:
這里必須要區(qū)分一下切片和切片引用,首先代碼中的 arr[2..5] 是一個(gè)切片,由于截取的范圍不同,那么切片的長度也不同。所以切片它不能夠分配在棧上,因?yàn)闂I系臄?shù)據(jù)必須有一個(gè)固定的、且在編譯期就能確定的大小,而切片的長度不固定,那么大小也不固定,因此它只能分配在堆上。
既然分配在堆上,那么就不能直接使用它,必須要通過引用。在 Rust 中,凡是堆上的數(shù)據(jù),都是通過棧上的引用訪問的,切片也不例外,而?&a[2..5] 便是切片引用。
切片引用是一個(gè)寬指針,里面存儲的是一個(gè)指針和一個(gè)長度,因此它不光可以是數(shù)組的切片,字符串也是可以的。
可能有人好奇?&arr[2..5] 和 &arr 有什么區(qū)別?首先在變量前面加上?& 表示獲取它的引用,并且是不可變引用,而加上 &mut,則表示獲取可變引用。注意:這里的可變引用中的可變兩個(gè)字,它指的不是引用本身是否可變,它描述的是能否通過引用去修改指向的值。
因此?&arr 表示對整個(gè)數(shù)組的引用,&arr[2..5] 表示對數(shù)組某個(gè)片段的引用。當(dāng)然如果截取的片段是整個(gè)數(shù)組,也就是 &arr[..],那么兩者是等價(jià)的。
然后再來思考一個(gè)問題,我們能不能通過切片引用修改底層數(shù)組呢?答案是可以的,只是對我們上面那個(gè)例子來說不可以。因?yàn)樯厦胬又械臄?shù)組是不可變的,所以我們需要聲明為可變。
fn?main()?{
????//?最終修改的還是數(shù)組,因此數(shù)組可變是前提 ????let?mut?arr?=?[1,?2,?3,?4,?5,?6]; ????//?但數(shù)組可變還不夠,引用也要是可變的 ????//?注意:只有當(dāng)變量是可變的,才能拿到它的可變引用 ????//?因?yàn)榭勺円玫暮x就是:允許通過引用修改指向的值 ????//?但如果變量本身不可變的話,那可變引用還有啥意義呢? ????//?因此?Rust?不允許我們獲取一個(gè)'不可變變量'的可變引用 ????let?slice?=?&mut?arr[2..5]; ????//?通過引用修改指向的值 ????slice[0]?=?11111; ????println!("{:?}",?arr); ????/* ????[1,?2,?11111,?4,?5,?6] ????*/ ????//?變量不可變,那么只能拿到它的不可變引用 ????//?而變量可變,那么不可變引用和可變引用,均可以獲取 ????//?下面的?slice?就是不可變引用 ????let?slice?=?&arr[2..5]; ????//?此時(shí)只能獲取元素,不能修改元素 ????//?因?yàn)?不可變引用'不支持通過引用去修改值 }
所以要想通過引用去修改值,那么不僅變量可變,還要獲取它的可變引用。
然后切片引用的類型?&[T],由于數(shù)組是 i32 類型,所以這里就是 &[i32]。
fn?main()?{
????let?mut?arr?=?[1,?2,?3,?4,?5,?6]; ????//?切片的不可變引用 ????let?slice:?&[i32]?=?&arr[2..5]; ????println!("{:?}",?slice);??//?[3,?4,?5] ????//?切片的可變引用 ????let?slice:?&mut?[i32]?=?&mut?arr[2..5]; ????println!("{:?}",?slice);??//?[3,?4,?5] ????//?注意這里的?slice?現(xiàn)在是可變引用,但它本身是不可變的 ????//?也就是說我們沒有辦法將一個(gè)別的切片引用賦值給它 ????//?slice?=?&mut?arr[2..6],這是不合法的 ????//?如果想這么做,那么?slice?本身也要是可變的 ????let?mut?slice:?&mut?[i32]?=?&mut?arr[2..5]; ????println!("{:?}",?slice);??//?[3,?4,?5] ????//?此時(shí)是允許的 ????slice?=?&mut?arr[2..6]; ????println!("{:?}",?slice);??//?[3,?4,?5,?6] }
以上便是 Rust 的切片,當(dāng)然我們不會直接使用切片,而是通過切片的引用。
自定義類型
Rust 允許我們通過 struct 和 enum 兩個(gè)關(guān)鍵字來自定義類型:
struct:定義一個(gè)結(jié)構(gòu)體;
enum:定義一個(gè)枚舉;
而常量可以通過 const 和 static 關(guān)鍵字來創(chuàng)建。
結(jié)構(gòu)體
結(jié)構(gòu)體有 3 種類型,分別是 C 風(fēng)格結(jié)構(gòu)體、元組結(jié)構(gòu)體、單元結(jié)構(gòu)體。先來看后兩種:
//?不帶有任何字段,一般用于 trait
struct?Unit; //?元組結(jié)構(gòu)體,相當(dāng)于給元組起了個(gè)名字 struct?Color(u8,?u8,?u8); fn?main()?{ ????//?單元結(jié)構(gòu)體實(shí)例 ????let?unit?=?Unit{}; ????//?元組結(jié)構(gòu)體實(shí)例 ????//?可以看到元組結(jié)構(gòu)體就相當(dāng)于給元組起了個(gè)名字 ????let?color?=?Color(255,?255,?137); ????println!( ????????"r?=?{},?g?=?{},?b?=?{}", ????????color.0,?color.1,?color.2 ????);?//?r?=?255,?g?=?255,?b?=?137 ????//?然后是元組結(jié)構(gòu)體的解構(gòu) ????let?Color(r,?g,?b)?=?color; ????println!("{}?{}?{}", ?????????????r,?g,?b);??//?255?255?137 }???????
注意最后元組結(jié)構(gòu)體實(shí)例的解構(gòu),普通元組的類型是 (T, ...),所以在解構(gòu)的時(shí)候通過 let (變量, ...)。但元組結(jié)構(gòu)體是 Color(T, ...),所以解構(gòu)的時(shí)候通過?let?Color(變量, ...)。
再來看看 C 風(fēng)格的結(jié)構(gòu)體。
//?C?風(fēng)格結(jié)構(gòu)體 #[derive(Debug)] struct?Point?{ ????x:?i32, ????y:?i32, } //?結(jié)構(gòu)體也可以嵌套 #[derive(Debug)] struct?Rectangle?{ ????//?矩形左上角和右下角的坐標(biāo) ????top_left:?Point, ????bottom_right:?Point, } fn?main()?{ ????let?p1?=?Point?{?x:?3,?y:?5?}; ????let?p2?=?Point?{?x:?6,?y:?10?}; ????//?訪問結(jié)構(gòu)體字段,通過?.?操作符 ????println!("{}",?p2.x);?//?6 ????//?以?Debug?方式打印結(jié)構(gòu)體實(shí)例 ????println!("{:?}",?p1);?//?Point?{?x:?3,?y:?5?} ????println!("{:?}",?p2);?//?Point?{?x:?6,?y:?10?} ????//?基于?Point?實(shí)例創(chuàng)建?Rectangle?實(shí)例 ????let?rect?=?Rectangle?{ ????????top_left:?p1, ????????bottom_right:?p2, ????}; ????//?計(jì)算矩形的面積 ????println!( ????????"area?=?{}", ????????(rect.bottom_right.y?-?rect.top_left.y)?* ????????(rect.bottom_right.x?-?rect.top_left.x) ????)??//?area?=?15 }
最后說一下 C 風(fēng)格結(jié)構(gòu)體的解構(gòu):
struct?Point?{ ????x:?i32, ????y:?f64, } fn?main()?{ ????let?p?=?Point?{?x:?3,?y:?5.2?}; ????//?用兩個(gè)變量保存?p?的兩個(gè)成員值,可以這么做 ????//?我們用到了元組,因?yàn)槎嘣x值本質(zhì)上就是元組的解構(gòu) ????let?(a,?b)?=?(p.x,?p.y); ????//?或者一個(gè)一個(gè)賦值也行 ????let?a?=?p.x; ????let?b?=?p.y; ????//?結(jié)構(gòu)體也支持解構(gòu) ????//?將?p.x?賦值給變量?a,將?p.y?賦值給變量?b ????let?Point?{?x:?a,?y:?b?}?=?p; ????println!("a?=?{},?b?=?{}", ?????????????a,?b);??//?a?=?3,?b?=?5.2 ????//?如果賦值的變量名,和結(jié)構(gòu)體成員的名字相同 ????//?那么還可以簡寫,比如這里要賦值的變量也叫?x、y ????//?以下寫法和?let?Point?{?x:?x,?y:?y?}?=?p?等價(jià) ????let?Point?{?x,?y?}?=?p; ????println!("x?=?{},?y?=?{}", ?????????????x,?y);??//?x?=?3,?y?=?5.2 }
最后,如果結(jié)構(gòu)體實(shí)例想改變的話,那么也要聲明為 mut。
枚舉
enum 關(guān)鍵字允許創(chuàng)建一個(gè)從數(shù)個(gè)不同取值中選其一的枚舉類型。
enum?Cell?{
????//?成員可以是單元結(jié)構(gòu)體 ????NULL, ????//?也可以是元組結(jié)構(gòu)體 ????Integer(i64), ????Floating(f64), ????DaysSales(u32,?u32,?u32,?u32,?u32), ????//?普通結(jié)構(gòu)體,或者說?C?風(fēng)格結(jié)構(gòu)體 ????TotalSales?{cash:?u32,?currency:?&'static?str} } fn?deal(c:?Cell)?{ ????match?c?{ ????????Cell::NULL?=>?println!("空"), ????????Cell::Integer(i)?=>?println!("{}",?i), ????????Cell::Floating(f)?=>?println!("{}",?f), ????????Cell::DaysSales(mon,?tues,?wed,?thur,?fri)?=>?{ ????????????println!("{}?{}?{}?{}?{}", ?????????????????????mon,?tues,?wed,?thur,?fri) ????????}, ????????Cell::TotalSales?{?cash,?currency?}?=>?{ ????????????println!("{}?{}",?cash,?currency) ????????} ????} } fn?main()?{ ????//?枚舉的任何一個(gè)成員,都是枚舉類型 ????let?c1:?Cell?=?Cell::NULL; ????let?c2:?Cell?=?Cell::Integer(123); ????let?c3:?Cell?=?Cell::Floating(3.14); ????let?c4?=?Cell::DaysSales(101,?111,?102,?93,?97); ????let?c5?=?Cell::TotalSales?{ ????????cash:??504,?currency:?"USD"}; ????deal(c1);??//?空 ????deal(c2);??//?123 ????deal(c3);??//?3.14 ????deal(c4);??//?101?111?102?93?97 ????deal(c5);??//?504?USD }
所以當(dāng)你要保存的數(shù)據(jù)的類型不確定,但屬于有限的幾個(gè)類型之一,那么枚舉就特別合適。另外枚舉在 Rust 里面占了非常高的地位,像空值處理、錯(cuò)誤處理都用到了枚舉。
然后是起別名,如果某個(gè)枚舉的名字特別長,那么我們可以給該枚舉類型起個(gè)別名。當(dāng)然啦,起別名不僅僅針對枚舉,其它類型也是可以的。
enum?GetElementByWhat?{
????Id(String), ????Class(String), ????Tag(String), } fn?main()?{ ????//?我們發(fā)現(xiàn)這樣寫起來特別的長 ????let?ele?=?GetElementByWhat::Id(String::from("submit")); ????//?于是可以起個(gè)別名 ????type?Element?=?GetElementByWhat; ????let?ele?=?Element::Id(String::from("submit")); }?
給類型起的別名應(yīng)該遵循駝峰命名法,起完之后就可以當(dāng)成某個(gè)具體的類型來用了。但要注意的是,類型別名并不能提供額外的類型安全,因?yàn)閯e名不是新的類型。
除了起別名之外,我們還可以使用 use 關(guān)鍵字直接將枚舉成員引入到當(dāng)前作用域。
enum?GetElementByWhat?{
????Id(String), ????Class(String), ????Tag(String), } fn?main()?{ ????//?將?GetElementByWhat?的?Id?成員引入到當(dāng)前作用域 ????use?GetElementByWhat::Id; ????let?ele?=?Id(String::from("submit")); ????//?也可以同時(shí)引入多個(gè) ????//?這種方式和一行一行寫是等價(jià)的 ????use?GetElementByWhat::{Class,?Tag}; ????//?如果你想全部引入的話,也可以使用通配符 ????use?GetElementByWhat::*; }
然后 enum 也可以像 C 語言的枚舉類型一樣使用。
//?這些枚舉成員都有隱式的值
//?Zero?等于?0,one?等于?1,Two?等于?2 enum?Number?{ ????Zero, ????One, ????Two, } fn?main()?{ ????//?既然是隱式的,就說明不能直接用 ????//?需要顯式地轉(zhuǎn)化一下 ????println!("Zero?is?{}",?Number::Zero?as?i32); ????println!("One?is?{}",?Number::One?as?i32); ????/* ????Zero?is?0 ????One?is?1 ????*/ ????let?two?=?Number::Two; ????match?two?{ ????????Number::Zero?=>?println!("Number::Zero"), ????????Number::One?=>?println!("Number::One"), ????????Number::Two?=>?println!("Number::Two"), ????} ????/* ????Number::Two ????*/ ????//?也可以轉(zhuǎn)成整數(shù) ????match?two?as?i32?{ ????????0?=>?println!("{}",?0), ????????1?=>?println!("{}",?1), ????????2?=>?println!("{}",?2), ????????//?雖然我們知道轉(zhuǎn)成整數(shù)之后 ????????//?可能的結(jié)果只有?0、1、2?三種,但?Rust?不知道 ????????//?所以還要有一個(gè)默認(rèn)值 ????????_?=>?unreachable!() ????} ????/* ????2 ????*/ }
既然枚舉成員都有隱式的值,那么可不可以有顯式的值呢?答案是可以的。
//?當(dāng)指定值的時(shí)候,值必須是?isize?類型
enum?Color?{ ????R?=?125, ????G?=?223, ????B, } fn?main()?{ ????println!("R?=?{}",?Color::R?as?u8); ????println!("G?=?{}",?Color::G?as?u8); ????println!("B?=?{}",?Color::B?as?u8); ????/* ????R?=?125 ????G?=?223 ????B?=?224 ????*/ }
枚舉的成員 B 沒有初始值,那么它默認(rèn)是上一個(gè)成員的值加 1。但需要注意的是,如果想實(shí)現(xiàn)具有 C 風(fēng)格的枚舉,那么必須滿足枚舉里面的成員都是單元結(jié)構(gòu)體。
//?這個(gè)枚舉是不合法的
//?需要將?B(u8)?改成?B enum?Color?{ ????R, ????G, ????B(u8), }
還是比較簡單的。
常量
Rust 的常量,可以在任意作用域聲明,包括全局作用域。
//?Rust?的常量名應(yīng)該全部大寫
//?并且聲明的時(shí)候必須提供類型,否則編譯錯(cuò)誤 const?AGE:?u16?=?17; //?注意:下面這種方式不行 //?因?yàn)檫@種方式本質(zhì)上還是在讓?Rust?做推斷 //?const?AGE?=?17u16; fn?main()?{ ????//?常量可以同時(shí)在全局和函數(shù)里面聲明 ????//?但變量只能在函數(shù)里面 ????const?NAME:?&str?=?"komeiji?satori"; ????println!("NAME?=?{}",?NAME); ????println!("AGE?=?{}",?AGE); ????/* ????NAME?=?komeiji?satori ????AGE?=?17 ????*/ }
注意:常量接收的必須是在編譯期間就能確定、且不變的值,我們不能把一個(gè)運(yùn)行時(shí)才能確定的值綁定在常量上。
fn?count?()?->?i32?{
????5 } fn?main()?{ ????//?合法,因?yàn)?5?是一個(gè)編譯期間可以確定的常量 ????const?COUNT1:?i32?=?5; ????//?下面也是合法的,像?3?+?2、4?*?8?這種,雖然涉及到了運(yùn)算 ????//?但運(yùn)算的部分都是常量,在編譯期間可以計(jì)算出來 ????//?所以會將?3?+?2?換成?5,將?4?*?8?換成?32 ????//?這個(gè)過程有一個(gè)專用術(shù)語,叫做常量折疊 ????const?COUNT2:?i32?=?3?+?2; ????//?但下面不行,count()?是運(yùn)行時(shí)執(zhí)行的 ????//?我們不能將它的返回值綁定在常量上 ????//?const?COUNT:?i32?=?count(); ????//?再比如數(shù)組,數(shù)組的長度也必須是常量,并且是?usize?類型 ????const?LENGTH:?usize?=?5; ????let?arr:?[i32;?LENGTH]?=?[1,?2,?3,?4,?5]; ????//?但如果將?const?換成?let?就不行了 ????//?因?yàn)閿?shù)組的長度是常量,而?let?聲明的是變量 ????//?因此以下代碼不合法 ????/* ????let?LENGTH:?usize?=?5; ????let?arr:?[i32;?LENGTH]?=?[1,?2,?3,?4,?5]; ????*/ }
另外我們使用 let 可以聲明多個(gè)同名變量,這在 Rust 里面叫做變量的隱藏。但常量不行,常量的名字必須是唯一的,而且也不能和變量重名。
除了 const,還有一個(gè) static,它聲明的是靜態(tài)變量。但它的生命周期和常量是等價(jià)的,都貫穿了程序執(zhí)行的始終。
//?靜態(tài)變量在聲明時(shí)同樣要顯式指定類型 static?AGE:?u8?=?17; //?常量是不可變的,所以它不可以使用?mut?關(guān)鍵字 //?即?const?mut?xxx?是不合法的,但?static?可以 //?因?yàn)?static?聲明的是變量,只不過它是靜態(tài)的 //?存活時(shí)間和常量是相同,都和執(zhí)行的程序共存亡 static?mut?NAME:?&str?=?"satori"; fn?main()?{ ????//?靜態(tài)變量也可以在函數(shù)內(nèi)部聲明和賦值 ????static?ADDRESS:?&str?=?"じれいでん"; ????println!("AGE?=?{}",?AGE); ????println!("ADDRESS?=?{}",?ADDRESS); ????/* ????AGE?=?17 ????ADDRESS?=?じれいでん ????*/ ????//?需要注意:靜態(tài)變量如果聲明為可變 ????//?那么在多線程的情況下可能造成數(shù)據(jù)競爭 ????//?因此使用的時(shí)候,需要放在?unsafe?塊里面 ????unsafe?{ ????????NAME?=?"koishi"; ????????println!("NAME?=?{}",?NAME); ????????/* ????????NAME?=?koishi ????????*/ ????} }
注意里面用到了 unsafe ,關(guān)于啥是 unsafe 我們后續(xù)再聊,總之靜態(tài)變量我們一般很少會聲明為可變。
變量綁定
接下來復(fù)習(xí)一下變量綁定,給變量賦值在 Rust 里面有一個(gè)專門的說法:將值綁定到變量上。都是一個(gè)意思,我們理解就好。
fn?main()?{ ????//?綁定操作通過?let?關(guān)鍵字實(shí)現(xiàn) ????//?將?u8?類型的?17?綁定在變量?age?上 ????let?age?=?17u8; ????//?將?age?拷貝給?age2 ????let?age2?=?age; }
如果變量聲明了但沒有使用,Rust 會拋出警告,我們可以在沒有使用的變量前面加上下劃線,來消除警告。
可變變量
變量默認(rèn)都是不可變的,我們可以在聲明的時(shí)候加上 mut 關(guān)鍵字讓其可變。
#[derive(Debug)]
struct?Color?{ ????R:?u8, ????G:?u8, ????B:?u8, } fn?main()?{ ????let?c?=?Color{R:?155,?G:?137,?B:?255}; ????//?變量?c?的前面沒有?mut,所以它不可變 ????//?我們不可以對?c?重新賦值,也不可以修改?c?里的成員值 ????//?如果想改變,需要使用?let?mut?聲明 ????let?mut?c?=?Color{R:?155,?G:?137,?B:?255}; ????println!("{:?}",?c); ????/* ????Color?{?R:?155,?G:?137,?B:?255?} ????*/ ????//?聲明為?mut?之后,我們可以對?c?重新賦值 ????c?=?Color{R:?255,?G:?52,?B:?102}; ????println!("{:?}",?c); ????/* ????Color?{?R:?255,?G:?52,?B:?102?} ????*/ ????//?當(dāng)然修改?c?的某個(gè)成員值也是可以的 ????c.R?=?0; ????println!("{:?}",?c); ????/* ????Color?{?R:?0,?G:?52,?B:?102?} ????*/ }所以要改變變量的值有兩種方式:
1)給變量賦一個(gè)新的值,這是所有變量都支持的,比如 let mut t = (1, 2),如果想將第一個(gè)元素改成 11,那么 t = (11, 2) 即可;
2)針對元組、數(shù)組、結(jié)構(gòu)體等,如果你熟悉 Python 的話,會發(fā)現(xiàn)這類似于 Python 里的可變對象。也就是不賦一個(gè)新的值,而是對當(dāng)前已有的值進(jìn)行修改,比如let mut?t = (1, 2),如果想將第一個(gè)元素改成 11,那么?t.0 = 11?即可。
但不管是將變量的值整體替換掉,還是對已有的值進(jìn)行修改,本質(zhì)上都是在改變變量的值。如果想改變,那么變量必須聲明為 mut。
作用域和隱藏
綁定的變量都有一個(gè)作用域,它被限制只能在一個(gè)代碼塊內(nèi)存活,其中代碼塊是一個(gè)被大括號包圍的語句集合。
fn?main()?{
????//?存活范圍是整個(gè)?main?函數(shù) ????let?name?=?"古明地覺"; ????{ ????????//?新的作用域,里面沒有?name?變量 ????????//?那么會從所在的外層作用域中尋找 ????????println!("{}",?name);??//?古明地覺 ????????//?創(chuàng)建了新的變量 ????????let?name?=?"古明地戀"; ????????let?age?=?16; ????????println!("{}",?name);??//?古明地戀 ????????println!("{}",?age);???//?16 ????} ????//?再次打印?name ????println!("{}",?name);??//?古明地覺 ????//?但變量?age?已經(jīng)不存在了 ????//?外層作用域創(chuàng)建的變量,內(nèi)層作用域也可以使用 ????//?但內(nèi)層作用域創(chuàng)建的變量,外層作用域不可以使用 }
?
?
我們上面創(chuàng)建了兩個(gè) name,但它們是在不同的作用域,所以彼此沒有關(guān)系。但如果在同一個(gè)作用域創(chuàng)建兩個(gè)同名的變量,那么后一個(gè)變量會將前一個(gè)變量隱藏掉。
fn?main()?{
????let?mut?name?=?"古明地覺"; ????println!("{}",?name);??//?古明地覺 ????//?這里的?name?前面沒有?let ????//?相當(dāng)于變量的重新賦值,因此值的類型要和之前一樣 ????//?并且?name?必須可變 ????name?=?"古明地戀"; ????println!("{}",?name);??//?"古明地戀" ????let?num?=?123; ????println!("{}",?num);??//?123 ????//?重新聲明?num,上一個(gè)?num?會被隱藏掉 ????//?并且兩個(gè)?num?沒有關(guān)系,是否可變、類型都可以自由指定 ????let?mut?num?=?345u16; ????println!("{}",?num);??//?345 }
變量的隱藏算是現(xiàn)代靜態(tài)語言中的一個(gè)比較獨(dú)特的特性了。
另外變量聲明的時(shí)候可以同時(shí)賦初始值,但將聲明和賦值分為兩步也是可以的。
fn?main()?{
????let?name; ????{ ????????//?當(dāng)前作用域沒有?name ????????//?那么綁定的就是外層的?name ????????name?=?"古明地覺" ????} ????println!("{}",?name);??//?古明地覺 ????//?注意:光看 name =?"古明地覺"?這行代碼的話 ????//?容易給人一種錯(cuò)覺,認(rèn)為?name?是可變的 ????//?但其實(shí)不是的,我們只是將聲明和賦值分成了兩步而已 ????//?如果再賦一次值的話就會報(bào)錯(cuò)了,因?yàn)槲覀冃薷牧艘粋€(gè)不可變的變量 ????//?name?=?"古明地戀";?//?不合法,因?yàn)樾薷牧瞬豢勺兊淖兞?}
如果變量聲明之后沒有賦初始值,那么該變量就是一個(gè)未初始化的變量。而 Rust 不允許使用未初始化的變量,因?yàn)闀a(chǎn)生未定義行為。
原生類型的轉(zhuǎn)換
接下來是類型轉(zhuǎn)換,首先 Rust 不提供原生類型之間的隱式轉(zhuǎn)換,如果想轉(zhuǎn)換,那么必須使用 as 關(guān)鍵字顯式轉(zhuǎn)換。
fn?main()?{
????let?pi?=?3.14f32; ????//?下面的語句是不合法的,因?yàn)轭愋筒煌?????//?let?int:?u8?=?pi ????//?Rust?不支持隱式轉(zhuǎn)換,但可以使用?as ????let?int:?u8?=?pi?as?u8; ????//?轉(zhuǎn)換之后會被截?cái)?????println!("{}?{}",?pi,?int);??//?3.14?3 ????//?整數(shù)也可以轉(zhuǎn)成?char?類型 ????let?char?=?97?as?char; ????println!("{}",?char);?//?a ????//?但是整數(shù)在轉(zhuǎn)化的時(shí)候要注意溢出的問題 ????//?以及無符號和有符號的問題 ????let?num?=?-10; ????//?u8?無法容納負(fù)數(shù),那么轉(zhuǎn)成?u8?的結(jié)果就是 ????//?2?的?8?次方?+?num ????println!("{}",?num?as?u8);??//?246 ????let?num?=?-300; ????//?-300?+?256?=?-44,但?-44?還小于?0 ????//?那么繼續(xù)加,-44?+?256?=?212 ????println!("{}",?num?as?u8);??//?212 ????//?轉(zhuǎn)成?u16?就是?2?的?16?次方?+?num ????println!("{}",?num?as?u16);??//?65526 ????//?以上有符號和無符號,然后是溢出的問題 ????let?num?=?300u16; ????println!("{}",?num?as?u8);??//?44 ????//?轉(zhuǎn)成?u8?相當(dāng)于只看最后?8?位 ????//?那么?num?as?u8?就等價(jià)于 ????println!("{}",?num?&?0xFF);??//?44 }
as 關(guān)鍵字只允許原生類型之間的轉(zhuǎn)換,如果你想把包含 4 個(gè)元素的 u8 數(shù)組轉(zhuǎn)成一個(gè) u32 整數(shù),那么 as 就不允許了。盡管在邏輯上這是成立的,但 Rust 覺得不安全,如果你非要轉(zhuǎn)的話,那么需要使用 Rust 提供的一種更高級的轉(zhuǎn)換,并且還要使用 unsafe。
fn?main()?{
????//?轉(zhuǎn)成二進(jìn)制的話就是 ????//?arr[0]?->?00000001 ????//?arr[1]?->?00000010 ????//?arr[2]?->?00000011 ????//?arr[3]?->?00000100 ????let?arr:?[u8;?4]?=?[1,?2,?3,?4]; ????//?4?個(gè)?u8?可以看成是一個(gè)?u32 ????//?由于?Rust?采用的是小端存儲 ????//?所以轉(zhuǎn)成整數(shù)就是 ????let?num?=?0b00000100_00000011_00000010_00000001; ????println!("{}",?num); ????//?我們也可以使用?Rust?提供的更高級的類型轉(zhuǎn)換 ????unsafe?{ ????????println!("{}",?std::<[u8;?4],?u32>(arr)) ????} ????/* ????67305985 ????67305985 ????*/ }
可以看到結(jié)果和我們想的是一樣的。然后關(guān)于 unsafe 這一塊暫時(shí)無需關(guān)注,包括里面那行復(fù)雜的類型轉(zhuǎn)換暫時(shí)也不用管,我們會在后續(xù)解釋它們,目前只需要知道有這么個(gè)東西即可。
自定義類型的轉(zhuǎn)換
看完了原生類型的轉(zhuǎn)換,再來看看自定義類型,也就是結(jié)構(gòu)體和枚舉。針對于自定義類型的轉(zhuǎn)換,Rust 是基于 trait 實(shí)現(xiàn)的,在 Rust 里面有一個(gè)叫 From 的 trait,它內(nèi)部定義了一個(gè) from 方法。
因此如果類型 T 實(shí)現(xiàn) From trait,那么通過 T::from 便可以基于其它類型的值生成自己。
#[derive(Debug)] struct?Number?{ ????val:?i32 } //?From?定義了一個(gè)泛型?T //?因此在實(shí)現(xiàn)?From?的時(shí)候還要指定泛型的具體類型 impl?From?for?Number?{ ????//?在調(diào)用?Number::from(xxx)?的時(shí)候 ????//?就會自動執(zhí)行這里的?from?方法 ????//?因?yàn)閷?shí)現(xiàn)的是?From ,那么?xxx?也必須是?i32 ????//?再注意一下這里的?Self,它表示的是當(dāng)前的結(jié)構(gòu)體類型 ????//?但顯然我們寫成?Number?也是可以的,不過更建議寫成?Self ????fn?from(item:?i32)?->?Self?{ ????????Number?{?val:?item?} ????} } fn?main()?{ ????println!("{:?}",?Number::from(666)); ????/* ????Number?{?val:?666?} ????*/ ????//?再比如?String::from,首先?String?也是個(gè)結(jié)構(gòu)體 ????//?顯然它實(shí)現(xiàn)了?From<&str> ????println!("{}",?String::from("你好")); ????/* ????你好 ????*/ }
既然有 From,那么就有 Into,Into 相當(dāng)于是把 From 給倒過來了。并且當(dāng)你實(shí)現(xiàn)了 From,那么自動就獲得了 Into。
#[derive(Debug)] struct?Number?{ ????val:?u16 } impl?From?for?Number?{ ????fn?from(item:?u16)?->?Self?{ ????????Number?{?val:?item?} ????} } fn?main()?{ ????println!("{:?}",?Number::from(666)); ????/* ????Number?{?val:?666?} ????*/ ????//?由于不同的類型都可以實(shí)現(xiàn)?From ?trait ????//?那么在調(diào)用?666u16.into()?的時(shí)候,編譯器就不知道轉(zhuǎn)成哪種類型 ????//?因此這里需要顯式地進(jìn)行類型聲明 ????let?n:?Number?=?666u16.into(); ????println!("{:?}",?n);??//?Number?{?val:?666?} }
另外里面的 666u16 寫成 666 也是可以的。因?yàn)檎{(diào)用了 into 方法,Rust 會根據(jù)上下文將其推斷為 u16。
但如果我們指定了類型,并且類型不是 u16,比如 666u8,那么就不行了。因?yàn)?Number 沒有實(shí)現(xiàn) From
然后除了 From 和 Into 之外,還有 TryFrom 和 TryInto,它們用于易出錯(cuò)的類型轉(zhuǎn)換,返回值是 Result 類型。我們看一下 TryFrom 的定義:
trait?TryFrom
????type?Error; ????fn?try_from(value:?T)?->?Result; }
如果簡化一下,那么就是這個(gè)樣子,我們需要實(shí)現(xiàn) try_from 方法,并且要給某個(gè)類型起一個(gè)別名叫 Error。
//?TryFrom?和?TryInto?需要先導(dǎo)入 use?std::TryFrom; use?std::TryInto; #[derive(Debug)] struct?IsAdult?{ ????age:?u8 } impl?TryFrom?for?IsAdult?{ ????type?Error?=?&'static?str; ????fn?try_from(item:?u8)?->?Result ?{ ????????if?item?>=?18?{ ????????????Ok(IsAdult{age:?item}) ????????}?else?{ ????????????Err("未成年") ????????} ????} } fn?main()?{ ????let?p1?=?IsAdult::try_from(18); ????let?p2?=?IsAdult::try_from(17); ????println!("{:?}",?p1); ????println!("{:?}",?p2); ????/* ????Ok(IsAdult?{?age:?18?}) ????Err("未成年") ????*/ ????//?實(shí)現(xiàn)了?TryFrom?也自動實(shí)現(xiàn)了?TryInto ????let?p3:?Result ?=?20.try_into(); ????let?p4:?Result ?=?15.try_into(); ????println!("{:?}",?p3); ????println!("{:?}",?p4); ????/* ????Ok(IsAdult?{?age:?20?}) ????Err("未成年") ????*/ }
最后再來介紹一個(gè)叫?ToString 的 trait,只要實(shí)現(xiàn)了這個(gè) trait,那么便可以調(diào)用 to_string 方法轉(zhuǎn)成字符串。因?yàn)椴还苁裁搭愋偷膶ο螅覀兌枷M軐⑺蛴〕鰜怼?/span>
use?std::ToString; struct?IsAdult?{ ????age:?u8 } //?ToString?不帶泛型參數(shù) //?只有一個(gè)?to_string?方法,我們實(shí)現(xiàn)它即可 impl?ToString?for?IsAdult?{ ????fn?to_string(&self)?->?String?{ ????????format!("age?=?{}",?self.age) ????} } fn?main()?{ ????let?p?=?IsAdult{age:?18}; ????println!("{}",?p.to_string()); ????/* ????age?=?18 ????*/ }
但很明顯,對于當(dāng)前這個(gè)例子來說,即使我們不實(shí)現(xiàn) trait、只是單純地實(shí)現(xiàn)一個(gè)方法也是可以的。
流程控制
任何一門編程語言都會包含流程控制,在 Rust 里面有 if/else, for, while, loop 等等,讓我們來看一看它們的用法。
if / else
Rust 的 if / else 和其它語言類似,但 Rust 的布爾判斷條件不必使用小括號包裹,且每個(gè)條件后面都跟著一個(gè)代碼塊。并且 if / else 是一個(gè)表達(dá)式,所有分支都必須返回相同的類型。
fn?degree(age:?u8)?->?String?{
????if?age?>?90?{ ????????//?&str?也實(shí)現(xiàn)了?ToString?trait ????????"A".to_string() ????}?else?if?age?>?80?{ ????????"B".to_string() ????}?else?if?age?>?60?{ ????????"C".to_string() ????}?else?{ ????????"D".to_string() ????} ????//?if?表達(dá)式的每一個(gè)分支都要返回相同的類型 ????//?然后執(zhí)行的某個(gè)分支的返回值會作為整個(gè)?if?表達(dá)式的值 } fn?main()?{ ????println!("{}",?degree(87)); ????println!("{}",?degree(97)); ????println!("{}",?degree(57)); ????/* ????B ????A ????D ????*/ }
Rust 沒有提供三元運(yùn)算符,因?yàn)樵?Rust 里面 if 是一個(gè)表達(dá)式,那么它可以輕松地實(shí)現(xiàn)三元運(yùn)算符。
fn?main()?{
????let?number?=?107; ????let?normailize?=?if?number?>?100?{100} ????????else?if?number?0?{0}?else?{number}; ????println!("{}",?normailize);?//?100 }
以上就是 Rust 的 if / else。
loop 循環(huán)
Rust 提供了?loop,不需要條件,表示無限循環(huán)。想要跳出的話,需要在循環(huán)內(nèi)部使用 break。
fn?main()?{
????let?mut?count?=?0; ????loop?{ ????????count?+=?1; ????????if?count?==?3?{ ????????????//?countinue?后面加不加分號均可 ????????????continue; ????????} ????????println!("count?=?{}",?count); ????????if?count?==?5?{ ????????????println!("ok,?that's?enough"); ????????????break; ????????} ????} ????/* ????count?=?1 ????count?=?2 ????count?=?4 ????count?=?5 ????ok,?that's?enough ????*/ }
最后 loop 循環(huán)有一個(gè)比較強(qiáng)大的功能,就是在使用 break 跳出循環(huán)的時(shí)候,break 后面的值會作為整個(gè) loop 循環(huán)的返回值。
fn?main()?{
????let?mut?count?=?0; ????let?result?=?loop?{ ????????count?+=?1; ????????if?count?==?3?{ ????????????continue; ????????} ????????if?count?==?5?{ ????????????break?1234567; ????????} ????}; ????println!("result?=?{}",?result); ????/* ????result?=?1234567 ????*/ }
這個(gè)特性還是很有意思的。
然后 loop 循環(huán)還支持打標(biāo)簽,可以更方便地跳出循環(huán)。
fn?main()?{
????let?mut?count?=?0; ????//?break?和?continue?針對的都是當(dāng)前所在的循環(huán) ????//?加上標(biāo)簽的話,即可作用指定的循環(huán) ????let?word?=?'outer:?loop?{ ????????println!("進(jìn)入外層循環(huán)"); ????????if?count?==?1?{ ????????????//?這里的?break?等價(jià)于?break?'outer ????????????println!("跳出外層循環(huán)"); ????????????break?"嘿嘿,結(jié)束了"; ????????} ????????'inner:?loop?{ ????????????println!("進(jìn)入內(nèi)層循環(huán)"); ????????????count?+=?1; ????????????//?這里如果只寫?continue ????????????//?那么等價(jià)于?continue?'inner ????????????continue?'outer; ????????}; ????}; ????/* ????進(jìn)入外層循環(huán) ????進(jìn)入內(nèi)層循環(huán) ????進(jìn)入外層循環(huán) ????跳出外層循環(huán) ????*/ ????println!("{}",?word); ????/* ????嘿嘿,結(jié)束了 ????*/ }
注意一下標(biāo)簽,和生命周期一樣,必須以一個(gè)單引號開頭。
for 循環(huán)
while 循環(huán)和其它語言類似,這里不贅述了,直接來看 for 循環(huán)。for 循環(huán)遍歷的一般都是迭代器,而創(chuàng)建迭代器最簡單的辦法就是使用區(qū)間標(biāo)記,比如 a..b,會生成從 a 到 b(不包含 b)、步長為 1 的一系列值。
fn?main()?{
????let?mut?sum?=?0; ????for?i?in?1..101?{ ????????sum?+=?i; ????} ????println!("{}",?sum);??//?5050 ????sum?=?0; ????//?如果是?..=,那么表示包含結(jié)尾 ????for?i?in?1..=100?{ ????????sum?+=?i; ????} ????println!("{}",?sum);??//?5050 }
然后再來說一說迭代器,for 循環(huán)在遍歷集合的時(shí)候,會自動調(diào)用集合的某個(gè)方法,將其轉(zhuǎn)換為迭代器,然后再遍歷,這一點(diǎn)和 Python 是比較相似的。那么都有哪些方法,調(diào)用之后可以得到集合的迭代器呢?
首先是 iter 方法,在遍歷的時(shí)候會得到元素的引用,這樣集合在遍歷結(jié)束之后仍可以使用。
fn?main()?{
????let?names?=?vec![ ????????"satori".to_string(), ????????"koishi".to_string(), ????????"marisa".to_string(), ????]; ????//?names?是分配在堆上的,如果遍歷的是?names ????//?那么遍歷結(jié)束之后?names?就不能再用了 ????//?因?yàn)樵诒闅v的時(shí)候,所有權(quán)就已經(jīng)發(fā)生轉(zhuǎn)移了 ????//?所以我們需要遍歷?names.iter() ????//?因?yàn)?names.iter()?獲取的是?names?的引用 ????//?而在遍歷的時(shí)候,拿到的也是每個(gè)元素的引用 ????for?name?in?names.iter()?{ ????????println!("{}",?name); ????} ????/* ????satori ????koishi ????marisa ????*/ ????println!("{:?}",?names); ????/* ????["satori",?"koishi",?"marisa"] ????*/ }
循環(huán)結(jié)束之后,依舊可以使用 names。
然后是 into_iter 方法,此方法會轉(zhuǎn)移所有權(quán),它和遍歷 names 是等價(jià)的。
我們看到在遍歷?names 的時(shí)候,會隱式地調(diào)用 names.into_iter()。如果后續(xù)不再使用 names,那么可以調(diào)用此方法,讓 names 將自身的所有權(quán)交出去。當(dāng)然啦,我們也可以直接遍歷 names,兩者是等價(jià)的。
最后是 iter_mut?方法,它和 iter 是類似的,只不過拿到的是可變引用。
fn?main()?{
????let?mut?numbers?=?vec![1,?2,?3]; ????//?numbers.iter()?獲取的是?numbers?的引用(不可變引用) ????//?然后遍歷得到的也是每個(gè)元素的引用(同樣是不可變引用) ????//?numbers.iter_mut()?獲取的是?numbers?的可變引用 ????//?然后遍歷得到的也是每個(gè)元素的可變引用 ????//?既然拿到的是可變引用,那么?numbers?必須要聲明為?mut ????for?number?in?numbers.iter_mut()?{ ????????//?這里的?number?就是?&mut i32 ????????//?修改引用指向的值 ????????*number?*=?2; ????} ????//?可以看到?numbers?變了 ????println!("{:?}",?numbers);??//?[2,?4,?6] }
以上就是創(chuàng)建迭代器的幾種方式,最后再補(bǔ)充一點(diǎn),迭代器還可以調(diào)用一個(gè) enumerate 方法,能夠?qū)⑺饕惨粔K返回。
fn?main()?{
????let?mut?names?=?vec![ ????????"satori".to_string(), ????????"koishi".to_string(), ????????"marisa".to_string(), ????]; ????for?(index,?name)?in?names.iter_mut().enumerate()?{ ????????name.push_str(&format!(",?我是索引?{}",?index)); ????} ????println!("{:#?}",?names); ????/* ????[ ????????"satori,?我是索引?0", ????????"koishi,?我是索引?1", ????????"marisa,?我是索引?2", ????] ????*/ }
調(diào)用 enumerate 方法之后,會將遍歷出來的值封裝成一個(gè)元組,其中第一個(gè)元素是索引。
match 匹配
Rust 通過 match 關(guān)鍵字來提供模式匹配,和 C 語言的 switch 用法類似。會執(zhí)行第一個(gè)匹配上的分支,并且所有可能的值必須都要覆蓋。
fn?main()?{
????let?number?=?20; ????match?number?{ ????????//?匹配單個(gè)值 ????????1?=>?println!("number?=?1"), ????????//?匹配多個(gè)值 ????????2?|?5?|?6?|?7?|?10?=>?{ ????????????println!("number?in?[2,?5,?6,?7,?10]") ????????}, ????????//?匹配一個(gè)區(qū)間范圍 ????????11..=19?=>?println!("11?<=?number?<=?19"), ????????//?match?要求分支必須覆蓋所有可能出現(xiàn)的情況 ????????//?但明顯數(shù)字是無窮的,于是我們可以使用下劃線代表默認(rèn)分支 ????????_?=>?println!("other?number") ????} ????/* ????other?number ????*/ ????let?flag?=?true; ????match?flag?{ ????????true?=>?println!("flag?is?true"), ????????false?=>?println!("flag?is?false"), ????????//?true?和?false?已經(jīng)包含了所有可能出現(xiàn)的情況 ????????//?因此下面的默認(rèn)分支是多余的,但可以有 ????????_?=>?println!("unreachable") ????} ????/* ????flag?is?true ????*/ }
對于數(shù)值和布爾值,我們更多用的是 if。然后 match 也可以處理更加復(fù)雜的結(jié)構(gòu),比如元組:
fn?main()?{
????let?t?=?(1,?2,?3); ????match?t?{ ????????(0,?y,?z)?=>?{ ????????????println!("第一個(gè)元素為?0,第二個(gè)元素為?{} ?????????????????????,第三個(gè)元素為?{}",?y,?z); ????????}, ????????//?使用 ..?可以忽略部分選項(xiàng),但 .. 只能出現(xiàn)一次 ????????//?(x,?..)?只關(guān)心第一個(gè)元素 ????????//?(..,?x)?只關(guān)心最后一個(gè)元素 ????????//?(x,?..,?y)?只關(guān)心第一個(gè)和最后一個(gè)元素 ????????//?(x,?..,?y,?z)?只關(guān)心第一個(gè)和最后兩個(gè)元素 ????????//?(..)?所有元素都不關(guān)心,此時(shí)效果等價(jià)于默認(rèn)分支 ????????(1,?..)?=>?{ ????????????println!("第一個(gè)元素為?1,其它元素不關(guān)心") ????????}, ????????(..)?=>?{ ????????????println!("所有元素都不關(guān)心") ????????}, ????????_?=>?{ ????????????//?由于?(..)?分支的存在,默認(rèn)分支永遠(yuǎn)不可能執(zhí)行 ????????????println!("默認(rèn)分支") ????????} ????} ????/* ????第一個(gè)元素為?1,其它元素不關(guān)心 ????*/ }
然后是枚舉:
fn?main()?{
????enum?Color?{ ????????RGB(u32,?u32,?u32), ????????HSV(u32,?u32,?u32), ????????HSL(u32,?u32,?u32), ????} ????let?color?=?Color::RGB(122,?45,?203); ????match?color?{ ????????Color::RGB(r,?g,?b)?=>?{ ????????????println!("r?=?{},?g?=?{},?b?=?{}", ?????????????????????r,?g,?b); ????????}, ????????Color::HSV(h,?s,?v)?=>?{ ????????????println!("h?=?{},?s?=?{},?v?=?{}", ?????????????????????h,?s,?v); ????????}, ????????Color::HSL(h,?s,?l)?=>?{ ????????????println!("h?=?{},?s?=?{},?l?=?{}", ?????????????????????h,?s,?l); ????????} ????} ????/* ????r?=?122,?g?=?45,?b?=?203 ????*/ }
接下來是結(jié)構(gòu)體:
fn?main()?{
????struct?Point?{ ????????x:?(u32,?u32), ????????y:?u32 ????} ????let?p?=?Point{x:?(1,?2),?y:?5}; ????//?之前說過,可以使用下面這種方式解構(gòu) ????//?let?Point?{?x,?y?}?=?p ????//?對于使用?match?來說,也是如此 ????match?p?{ ????????Point?{?x,?y?}?=>?{ ????????????println!("p.x?=?{:?},?p.y?=?{}",?x,?y); ????????} ????????//?如果不關(guān)心某些成員的話,那么也可以使用?.. ????????//?比如?Point?{x,?..},表示你不關(guān)心?y ????} ????/* ????p.x?=?(1,?2),?p.y?=?5 ????*/ }最后來看一下,如何對引用進(jìn)行解構(gòu)。首先要注意的是:解引用和解構(gòu)是兩個(gè)完全不同的概念。解引用使用的是 *,解構(gòu)使用的是 &。
fn?main()?{
????let?mut?num?=?123; ????//?獲取一個(gè)?i32?的引用 ????let?refer?=?&mut?num; ????//?refer?是一個(gè)引用,可以通過?*refer?解引用 ????//?并且在打印的時(shí)候,refer?和?*refer?是等價(jià)的 ????println!("refer?=?{},?*refer?=?{}",?refer,?*refer); ????/* ????refer?=?123,?*refer?=?123 ????*/ ????//?也可以修改引用指向的值 ????//?refer?引用的是?num,那么要想修改的話 ????//?num?必須可變,refer?也必須是?num?的可變引用 ????*refer?=?1234; ????println!("num?=?{}",?num); ????/* ????num?=?1234 ????*/ ????//?字符串也是同理 ????let?mut?name?=?"komeiji".to_string(); ????let?refer?=?&mut?name; ????//?修改字符串,將首字母大寫 ????*refer?=?"Komeiji".to_string(); ????println!("{}",?name);??//?Komeiji }
以上便是解引用,再來看看引用的解構(gòu)。
fn?main()?{
????let?num?=?123; ????let?refer?=?# ????match?refer?{ ????????//?如果用?&val?這個(gè)模式去匹配?refer ????????//?相當(dāng)于做了這樣的比較,因?yàn)?refer?是?&i32 ????????//?而模式是?&val,那么相當(dāng)于將?refer?引用的值拷貝給了?val ????????&val?=>?{ ????????????println!("refer?引用的值?=?{}",?val) ????????}??//?如果?refer?是可變引用,那么這里的模式就應(yīng)該是?&mut?val ????}; ????/* ????refer?引用的值?=?123 ????*/ ????//?如果不想使用?&,那么就要在匹配的時(shí)候解引用 ????match?*refer?{ ????????val?=>?{ ????????????println!("refer?引用的值?=?{}",?val) ????????} ????}; ????/* ????refer?引用的值?=?123 ????*/ }
最后我們創(chuàng)建引用的時(shí)候,除了可以使用 & 之外,還可以使用 ref 關(guān)鍵字。
fn?main()?{ ????let?num?=?123; ????//?let?refer?=?#?可以寫成如下 ????let?ref?refer?=?num; ????println!("{}?{}?{}",?refer,?*refer,?num); ????/* ????123?123?123 ????*/ ????//?引用和具體的值在打印上是沒有區(qū)別的 ????//?但從結(jié)構(gòu)上來說,兩者卻有很大區(qū)別 ????//?比如我們可以對?refer?解引用,但不能對?num?解引用 ????//?創(chuàng)建可變引用 ????let?mut?num?=?345; ????{ ????????let?ref?mut?refer?=?num; ????????*refer?=?*refer?+?1; ????????println!("{}?{}",?refer,?*refer); ????????/* ????????346?346 ????????*/ ????} ????println!("{}",?num);?//?346 ????//?然后模式匹配也可以使用?ref ????let?num?=?567; ????match?num?{ ????????//?此時(shí)我們應(yīng)該把?ref?refer?看成是一個(gè)整體 ????????//?所以?ref?refer?整體是一個(gè)?i32 ????????//?那么 refer 是啥呢?顯然是?&i32 ????????ref?refer?=>?println!("{}?{}",?refer,?*refer), ????} ????/* ????567?567 ????*/ ????let?mut?num?=?678; ????match?num?{ ????????//?顯然?refer?就是?&mut?i32 ????????ref?mut?refer?=>?{ ????????????*refer?=?789; ????????} ????} ????println!("{}",?num);?//?789 }
以上就是 match 匹配,但是在引用這一塊,需要多體會一下。
另外在使用 match 的時(shí)候,還可以搭配衛(wèi)語句,用于過濾分支,舉個(gè)例子:
fn?match_tuple(t:?(i32,?i32))?{
????match?t?{ ????????//?(x,?y)?已經(jīng)包含了所有的情況 ????????//?但我們又給它加了一個(gè)限制條件 ????????//?就是兩個(gè)元素必須相等 ????????(x,?y)?if?x?==?y?=>?{ ????????????println!("t[0]?==?t[1]") ????????}, ????????(x,?y)?if?x?>?y?=>?{ ????????????println!("t[0]?>?t[1]") ????????}, ????????//?此時(shí)就不需要衛(wèi)語句了,該分支的?x?一定小于?y ????????//?并且這里加上衛(wèi)語句反而會報(bào)錯(cuò),因?yàn)榧由现?????????//?Rust?無法判斷分支是否覆蓋了所有的情況 ????????//?所以必須有?(x,?y)?或者默認(rèn)分支進(jìn)行兜底 ????????(x,?y)?=>?{ ????????????println!("t[0]??t[1] ????*/ }
總的來說,衛(wèi)語句用不用都是可以的,我們完全可以寫成?(x, y),匹配上之后在分支里面做判斷。
最后 match 還有一個(gè)綁定的概念,看個(gè)例子:
fn?main()?{ ????let?num?=?520; ????match?num?{ ????????//?該分支一定可以匹配上 ????????//?匹配之后會將?num?賦值給?n ????????n?=>?{ ????????????if?n?==?520?{ ????????????????println!("{}?代表??(^_-)",?n) ????????????}?else?{ ????????????????println!("意義不明的數(shù)字") ????????????} ????????} ????} ????/* ????520?代表??(^_-) ????*/ ????//?我們可以將?520?這個(gè)分支單獨(dú)拿出來 ????match?num?{ ????????//?匹配完之后,會自動將?520?綁定在?n 上面 ????????n?@?520?=>?println!("{}?代表??(^_-)",?n), ????????n?=>?println!("意義不明的數(shù)字") ????} ????/* ????520?代表??(^_-) ????*/ ????//?當(dāng)然啦,我們還可以使用衛(wèi)語句 ????match?num?{ ????????n?if?n?==?520?=>?println!("{}?代表??(^_-)",?n), ????????n?=>?println!("意義不明的數(shù)字") ????} ????/* ????520?代表??(^_-) ????*/ }
這幾個(gè)功能彼此之間都是很相似的,用哪個(gè)都可以。
if let
在一些簡單的場景下,使用match 其實(shí)并不優(yōu)雅,舉個(gè)例子。
fn?main()?{
????let?num?=?Some(777); ????match?num?{ ????????Some(n)?=>?println!("{}",?n), ????????//?因?yàn)?match?要覆蓋所有情況,所以這一行必須要有 ????????//?但如果我們不關(guān)心默認(rèn)情況的話,那么就有點(diǎn)多余了 ????????_?=>?() ????} ????/* ????777 ????*/ ????//?所以當(dāng)我們只關(guān)心一種情況,其它情況忽略的話 ????//?那么使用?if?let?會更加簡潔 ????if?let?Some(i)?=?num?{ ????????println!("{}",?i); ????} ????/* ????777 ????*/ ????//?當(dāng)然?if?let?也支持?else?if?let?和?else ????let?score?=?78; ????if?let?x?@?90..=100?=?score?{ ????????println!("你的分?jǐn)?shù)?{}?屬于?A?級",?x) ????}?else?if?let?x?@?80..=89?=?score?{ ????????println!("你的分?jǐn)?shù)?{}?屬于?B?級",?x) ????}?else?if?let?60..=79?=?score?{ ????????println!("你的分?jǐn)?shù)?{}?屬于?C?級",?score) ????} ????/* ????你的分?jǐn)?shù)?78?屬于?C?級 ????*/ ????//?顯然對于當(dāng)前這種情況就不適合用?if?let?了 ????//?此時(shí)應(yīng)該使用?match?或者普通的?if?語句 ????//?總之:match 一般用來處理枚舉 ????//?如果不是枚舉,那么用普通的?if?else?就好 ????//?如果只關(guān)注枚舉的一種情況,那么使用?if?let }
注意:if let 也可以搭配 else if 語句。
小結(jié)
以上我們就回顧了一下 Rust 的基礎(chǔ)知識,包括原生類型、自定義類型、變量綁定、類型系統(tǒng)、類型轉(zhuǎn)換、流程控制。下一篇文章我們來回顧 Rust 的函數(shù)和泛型。
編輯:黃飛
?
評論