楔子
關(guān)于 Rust 的基礎(chǔ)知識(shí)我們已經(jīng)介紹一部分了,下面來做一個(gè)總結(jié)。因?yàn)?Rust 是一門難度非常高的語言,在學(xué)習(xí)完每一個(gè)階段之后,對(duì)學(xué)過的內(nèi)容適當(dāng)總結(jié)一下是很有必要的。
那么下面就開始吧,將以前說過的內(nèi)容再總結(jié)一遍,并且在這個(gè)過程中還會(huì)補(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è)自動(dòng)推斷功能,會(huì)結(jié)合上下文推斷數(shù)值的類型。
fn?main(){
????//?本來默認(rèn)?x?為?i32,y?為?f64 ????let?x?=?123; ????let?y?=?3.14; ????//?但是這里我們將?x,?y?組合成元組賦值給了?t ????//?而?t?是?(u8,?f32),所以?Rust?會(huì)結(jié)合上下文 ????//?將?x?推斷成?u8,將?y?推斷成?f32 ????let?t:?(u8,?f32)?=?(x,?y); }
但如果我們?cè)趧?chuàng)建 x 和 y 的時(shí)候顯式地規(guī)定了類型,比如將 x 聲明為 u16,那么代碼就不合法了。因?yàn)?t 的第一個(gè)元素是 u8,但傳遞的 x 卻是 u16,此時(shí)就會(huì)報(bào)錯(cuò),舉個(gè)例子:

Rust 對(duì)類型的要求非常嚴(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è)可以包含各種類型值的組合,使用括號(hào)來創(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?的類型就會(huì)被推斷為 ????//?((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,
????)
????*/
}
有了元組之后,還可以對(duì)其進(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í)候使用的是小括號(hào),但我們知道小括號(hào)也可以起到一個(gè)限定優(yōu)先級(jí)的作用。因此當(dāng)元組只有一個(gè)元素的時(shí)候,要顯式地在第一個(gè)元素后面加上一個(gè)逗號(hào)。
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 的對(duì)象的集合,在內(nèi)存中是連續(xù)存儲(chǔ)的,所以數(shù)組不僅要求長度固定,每個(gè)元素類型也必須一樣。數(shù)組使用中括號(hào)來創(chuàng)建,且它們的大小在編譯時(shí)會(huì)被確定。
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]
????*/
????//?不指定類型,可以自動(dòng)推斷出來
????//?此時(shí)會(huì)被推斷為?[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ù)組所包含的元素都是相同的,那么有一種簡(jiǎn)便的創(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)。切片允許我們對(duì)數(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è)寬指針,里面存儲(chǔ)的是一個(gè)指針和一個(gè)長度,因此它不光可以是數(shù)組的切片,字符串也是可以的。
可能有人好奇?&arr[2..5] 和 &arr 有什么區(qū)別?首先在變量前面加上?& 表示獲取它的引用,并且是不可變引用,而加上 &mut,則表示獲取可變引用。注意:這里的可變引用中的可變兩個(gè)字,它指的不是引用本身是否可變,它描述的是能否通過引用去修改指向的值。
因此?&arr 表示對(duì)整個(gè)數(shù)組的引用,&arr[2..5] 表示對(duì)數(shù)組某個(gè)片段的引用。當(dāng)然如果截取的片段是整個(gè)數(shù)組,也就是 &arr[..],那么兩者是等價(jià)的。
然后再來思考一個(gè)問題,我們能不能通過切片引用修改底層數(shù)組呢?答案是可以的,只是對(duì)我們上面那個(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)然我們不會(huì)直接使用切片,而是通過切片的引用。
自定義類型
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)體成員的名字相同
????//?那么還可以簡(jiǎn)寫,比如這里要賦值的變量也叫?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)然啦,起別名不僅僅針對(duì)枚舉,其它類型也是可以的。
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),
}
還是比較簡(jiǎn)單的。
常量
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ì)算出來
????//?所以會(huì)將?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ù)競(jìng)爭(zhēng)
????//?因此使用的時(shí)候,需要放在?unsafe?塊里面
????unsafe?{
????????NAME?=?"koishi";
????????println!("NAME?=?{}",?NAME);
????????/*
????????NAME?=?koishi
????????*/
????}
}
注意里面用到了 unsafe ,關(guān)于啥是 unsafe 我們后續(xù)再聊,總之靜態(tài)變量我們一般很少會(huì)聲明為可變。
變量綁定
接下來復(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 會(huì)拋出警告,我們可以在沒有使用的變量前面加上下劃線,來消除警告。
可變變量
變量默認(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,所以它不可變
????//?我們不可以對(duì)?c?重新賦值,也不可以修改?c?里的成員值
????//?如果想改變,需要使用?let?mut?聲明
????let?mut?c?=?Color{R:?155,?G:?137,?B:?255};
????println!("{:?}",?c);
????/*
????Color?{?R:?155,?G:?137,?B:?255?}
????*/
????//?聲明為?mut?之后,我們可以對(duì)?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)針對(duì)元組、數(shù)組、結(jié)構(gòu)體等,如果你熟悉 Python 的話,會(huì)發(fā)現(xiàn)這類似于 Python 里的可變對(duì)象。也就是不賦一個(gè)新的值,而是對(duì)當(dāng)前已有的值進(jìn)行修改,比如let mut?t = (1, 2),如果想將第一個(gè)元素改成 11,那么?t.0 = 11?即可。
但不管是將變量的值整體替換掉,還是對(duì)已有的值進(jìn)行修改,本質(zhì)上都是在改變變量的值。如果想改變,那么變量必須聲明為 mut。
作用域和隱藏
綁定的變量都有一個(gè)作用域,它被限制只能在一個(gè)代碼塊內(nèi)存活,其中代碼塊是一個(gè)被大括號(hào)包圍的語句集合。
fn?main()?{
????//?存活范圍是整個(gè)?main?函數(shù)
????let?name?=?"古明地覺";
????{
????????//?新的作用域,里面沒有?name?變量
????????//?那么會(huì)從所在的外層作用域中尋找
????????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è)變量會(huì)將前一個(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?會(huì)被隱藏掉
????//?并且兩個(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í)不是的,我們只是將聲明和賦值分成了兩步而已
????//?如果再賦一次值的話就會(huì)報(bào)錯(cuò)了,因?yàn)槲覀冃薷牧艘粋€(gè)不可變的變量
????//?name?=?"古明地戀";?//?不合法,因?yàn)樾薷牧瞬豢勺兊淖兞?}
如果變量聲明之后沒有賦初始值,那么該變量就是一個(gè)未初始化的變量。而 Rust 不允許使用未初始化的變量,因?yàn)闀?huì)產(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)換之后會(huì)被截?cái)?????println!("{}?{}",?pi,?int);??//?3.14?3 ????//?整數(shù)也可以轉(zhuǎn)成?char?類型 ????let?char?=?97?as?char; ????println!("{}",?char);?//?a ????//?但是整數(shù)在轉(zhuǎn)化的時(shí)候要注意溢出的問題 ????//?以及無符號(hào)和有符號(hào)的問題 ????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 ????//?以上有符號(hào)和無符號(hào),然后是溢出的問題 ????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 提供的一種更高級(jí)的轉(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?采用的是小端存儲(chǔ)
????//?所以轉(zhuǎn)成整數(shù)就是
????let?num?=?0b00000100_00000011_00000010_00000001;
????println!("{}",?num);
????//?我們也可以使用?Rust?提供的更高級(jí)的類型轉(zhuǎn)換
????unsafe?{
????????println!("{}",?std::<[u8;?4],?u32>(arr))
????}
????/*
????67305985
????67305985
????*/
}
可以看到結(jié)果和我們想的是一樣的。然后關(guān)于 unsafe 這一塊暫時(shí)無需關(guān)注,包括里面那行復(fù)雜的類型轉(zhuǎn)換暫時(shí)也不用管,我們會(huì)在后續(xù)解釋它們,目前只需要知道有這么個(gè)東西即可。
自定義類型的轉(zhuǎn)換
看完了原生類型的轉(zhuǎn)換,再來看看自定義類型,也就是結(jié)構(gòu)體和枚舉。針對(duì)于自定義類型的轉(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í)候
????//?就會(huì)自動(dòng)執(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,那么自動(dòng)就獲得了 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 會(huì)根據(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; }
如果簡(jiǎn)化一下,那么就是這個(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?也自動(dòng)實(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)椴还苁裁搭愋偷膶?duì)象,我們都希望能將它打印出來。
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
????*/
}
但很明顯,對(duì)于當(dāng)前這個(gè)例子來說,即使我們不實(shí)現(xiàn) trait、只是單純地實(shí)現(xiàn)一個(gè)方法也是可以的。
流程控制
任何一門編程語言都會(huì)包含流程控制,在 Rust 里面有 if/else, for, while, loop 等等,讓我們來看一看它們的用法。
if / else
Rust 的 if / else 和其它語言類似,但 Rust 的布爾判斷條件不必使用小括號(hào)包裹,且每個(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è)分支的返回值會(huì)作為整個(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?后面加不加分號(hào)均可
????????????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 后面的值會(huì)作為整個(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?針對(duì)的都是當(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è)單引號(hào)開頭。
for 循環(huán)
while 循環(huán)和其它語言類似,這里不贅述了,直接來看 for 循環(huán)。for 循環(huán)遍歷的一般都是迭代器,而創(chuàng)建迭代器最簡(jiǎn)單的辦法就是使用區(qū)間標(biāo)記,比如 a..b,會(huì)生成從 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í)候,會(huì)自動(dòng)調(diào)用集合的某個(gè)方法,將其轉(zhuǎn)換為迭代器,然后再遍歷,這一點(diǎn)和 Python 是比較相似的。那么都有哪些方法,調(diào)用之后可以得到集合的迭代器呢?
首先是 iter 方法,在遍歷的時(shí)候會(huì)得到元素的引用,這樣集合在遍歷結(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 方法,此方法會(huì)轉(zhuǎn)移所有權(quán),它和遍歷 names 是等價(jià)的。

我們看到在遍歷?names 的時(shí)候,會(huì)隱式地調(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 方法之后,會(huì)將遍歷出來的值封裝成一個(gè)元組,其中第一個(gè)元素是索引。
match 匹配
Rust 通過 match 關(guān)鍵字來提供模式匹配,和 C 語言的 switch 用法類似。會(huì)執(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
????*/
}
對(duì)于數(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
????//?對(duì)于使用?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
????*/
}最后來看一下,如何對(duì)引用進(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ū)別
????//?比如我們可以對(duì)?refer?解引用,但不能對(duì)?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 匹配,但是在引用這一塊,需要多體會(huì)一下。
另外在使用 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)語句反而會(huì)報(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?{
????????//?該分支一定可以匹配上
????????//?匹配之后會(huì)將?num?賦值給?n
????????n?=>?{
????????????if?n?==?520?{
????????????????println!("{}?代表??(^_-)",?n)
????????????}?else?{
????????????????println!("意義不明的數(shù)字")
????????????}
????????}
????}
????/*
????520?代表??(^_-)
????*/
????//?我們可以將?520?這個(gè)分支單獨(dú)拿出來
????match?num?{
????????//?匹配完之后,會(huì)自動(dòng)將?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
在一些簡(jiǎn)單的場(chǎng)景下,使用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?會(huì)更加簡(jiǎn)潔
????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?級(jí)",?x)
????}?else?if?let?x?@?80..=89?=?score?{
????????println!("你的分?jǐn)?shù)?{}?屬于?B?級(jí)",?x)
????}?else?if?let?60..=79?=?score?{
????????println!("你的分?jǐn)?shù)?{}?屬于?C?級(jí)",?score)
????}
????/*
????你的分?jǐn)?shù)?78?屬于?C?級(jí)
????*/
????//?顯然對(duì)于當(dāng)前這種情況就不適合用?if?let?了
????//?此時(shí)應(yīng)該使用?match?或者普通的?if?語句
????//?總之:match 一般用來處理枚舉
????//?如果不是枚舉,那么用普通的?if?else?就好
????//?如果只關(guān)注枚舉的一種情況,那么使用?if?let
}
注意:if let 也可以搭配 else if 語句。
小結(jié)
以上我們就回顧了一下 Rust 的基礎(chǔ)知識(shí),包括原生類型、自定義類型、變量綁定、類型系統(tǒng)、類型轉(zhuǎn)換、流程控制。下一篇文章我們來回顧 Rust 的函數(shù)和泛型。
編輯:黃飛
?
電子發(fā)燒友App
















評(píng)論