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