通用編程概念
變量與可變性
- 變量默認(rèn)不可變,如需要改變,可在變量名前加
mut使其可變。例如:let mut a=1;。 - 常量總是不能改變,使用
const聲明,并且必須注明值的類型。例如:const MAX_NUM: u32 = 100;。 - 可以定義一個(gè)與之前變量同名的新變量,從而“遮蓋”之前的變量,如下:
let x = 5;
let x = 6;
let x = x * 2;
println!("The value of x is: {}", x); // 12
mut與“遮蓋”的區(qū)別是,當(dāng)再次使用let時(shí),實(shí)際上創(chuàng)建了一個(gè)新變量,可以改變值的類型。而mut還是原來的變量,不可以改變類型。
數(shù)據(jù)類型
Rust是靜態(tài)類型語言,任何值都屬于一種明確的類型,內(nèi)建類型分為:標(biāo)量(scalar) 和 復(fù)合(compound)。
標(biāo)量類型
標(biāo)量 類型代表一個(gè)單獨(dú)的值。Rust 有四種基本的標(biāo)量類型:整型、浮點(diǎn)型、布爾類型、和字符類型。
- 整型
整數(shù)是一個(gè)沒有小數(shù)部分的數(shù)字,每一種變體都可以是有符號(hào)或無符號(hào)的,并有一個(gè)明確的大小。有符號(hào)和無符號(hào)代表數(shù)字能否為負(fù)值。
| 長度 | 有符號(hào) | 無符號(hào) |
|---|---|---|
| 8-bit | i8 | u8 |
| 16-bit | i16 | u16 |
| 32-bit | i32 | u32 |
| 64-bit | i64 | u64 |
| arch | isize | usize |
isize和usize類型依賴運(yùn)行程序的計(jì)算機(jī)架構(gòu):64 位架構(gòu)上它們是 64 位的, 32 位架構(gòu)上它們是 32 位的。
| 數(shù)字字面值 | 示例 |
|---|---|
| 十進(jìn)制 | 98_222 |
| 十六進(jìn)制 | 0xff |
| 八進(jìn)制 | 0o77 |
| 二進(jìn)制 | 0b111_000 |
| 字節(jié)(u8) | b'a' |
可以使用表格中的任何一種形式編寫數(shù)字字面值。除字節(jié)以外 的其它字面值允許使用 類型后綴,例如
57u8,允許使用_做為分隔符以方便讀數(shù),例如1_000(分隔符的數(shù)量與位置并不影響實(shí)際的數(shù)字)。
Rust 默認(rèn)數(shù)字類型是 i32:它通常是最快的,甚至在 64 位系統(tǒng)上也是。isize 或 usize 的主要作為集合的索引。
- 浮點(diǎn)型
Rust 同樣有兩個(gè)主要的浮點(diǎn)數(shù)類型:f32(單精度浮點(diǎn)數(shù)) 和 f64(雙精度浮點(diǎn)數(shù)),分別占 32 位和 64 位比特。默認(rèn)類型是 f64。因?yàn)樗c f32 速度差不多,然而精度更高。在 32 位系統(tǒng)上也能夠使用 f64。不過比使用 f32 要慢。
- 布爾型
Rust 中的布爾類型有兩個(gè):true 和 false。Rust 中的布爾類型使用 bool 表示。
- 字符類型
Rust 的 char 類型代表了一個(gè) Unicode 標(biāo)量值。這意味著它可以比 ASCII 表示更多內(nèi)容。拼音字母、中文/日文/漢語等象形文字、emoji(絵文字)以及零長度的空白字符對(duì)于 Rust char類型都是有效的。Unicode 標(biāo)量值包含從 U+0000 到 U+D7FF 和 U+E000 到 U+10FFFF 之間的值?!白址辈⒉皇且粋€(gè) Unicode 中的概念,Rust 中的 char類型與直覺上的“字符”可能并不符合。
復(fù)合類型
復(fù)合類型 可以將多個(gè)其它類型的值組合成一個(gè)類型。Rust 有兩個(gè)原生的復(fù)合類型:元組(tuple) 和 數(shù)組(array)。
- 元組
元組是一個(gè)將多個(gè)其它類型的值組合進(jìn)一個(gè)復(fù)合類型的主要方式。
元組中的每一個(gè)位置都有一個(gè)類型,類型不必相同。
為了從元組中獲取單個(gè)的值,可以使用模式匹配(pattern matching)來解構(gòu)(destructure )元組。
let tup = (500, 6.4, 1);
let (x, y, z) = tup;
println!("The value of y is: {}", y); // 6.4
除了使用模式匹配解構(gòu)之外,也可以使用點(diǎn)號(hào)(.)后跟值的索引來直接訪問它們,元組的第一個(gè)索引值是 0。例如:
let x: (i32, f64, u8) = (500, 6.4, 1);
let five_hundred = x.0;
let six_point_four = x.1;
let one = x.2;
- 數(shù)組
數(shù)組中的每個(gè)元素的類型必須相同。
Rust 中的數(shù)組是 固定長度 的:一旦聲明,它們的長度不能增長或縮小。
let a = [1, 2, 3, 4, 5];
數(shù)組在想要在 棧 (stack)上分配空間,或者是想要確??偸怯泄潭〝?shù)量的元素時(shí)十分有用。雖然它并不如 vector 類型那么靈活。vector 類型是標(biāo)準(zhǔn)庫提供的一個(gè)允許增長和縮小長度的類似數(shù)組的集合類型。當(dāng)不確定是應(yīng)該使用數(shù)組還是 vector 的時(shí)候,你可能應(yīng)該使用 vector。
數(shù)組是一整塊分配在棧上的內(nèi)存??梢允褂盟饕齺碓L問數(shù)組的元素,像這樣:
let a = [1, 2, 3, 4, 5];
let first = a[0];
let second = a[1];
當(dāng)嘗試用索引訪問一個(gè)元素時(shí),Rust 會(huì)檢查指定的索引是否小于數(shù)組的長度。如果索引超出了數(shù)組長度,Rust 會(huì)panic,它用于程序因?yàn)殄e(cuò)誤而退出的情況。
函數(shù)
Rust 使用 main 函數(shù)作為程序的入口。使用 fn 關(guān)鍵字類聲明函數(shù)。使用 “snake case” 作為函數(shù)和變量的命名規(guī)范——所有字母都小寫并使用下劃線分割。
在函數(shù)簽名中,必須聲明每個(gè)參數(shù)的類型。這是 Rust 設(shè)計(jì)中一個(gè)經(jīng)過慎重考慮的決定:要求在函數(shù)定義中提供類型注解意味著編譯器再也不需要在別的地方要求你注明類型就能知道你的意圖。
語句與表達(dá)式
Rust 是一個(gè)基于表達(dá)式的語言。
語句(Statements) 是執(zhí)行一些操作但不返回值的指令。函數(shù)定義也是語句,語句并不返回值。不能把語句賦值給一個(gè)變量。
表達(dá)式(Expressions) 計(jì)算并產(chǎn)生一個(gè)值。函數(shù)調(diào)用是一個(gè)表達(dá)式,宏調(diào)用是一個(gè)表達(dá)式,用來創(chuàng)新建作用域的大括號(hào)(代碼塊)
{}也是一個(gè)表達(dá)式。表達(dá)式并不包含結(jié)尾的分號(hào)。如果在表達(dá)式的結(jié)尾加上分號(hào),就變成了語句。
函數(shù)的返回值
可以向調(diào)用它的代碼返回值,并不對(duì)返回值命名,不過會(huì)在一個(gè)箭頭(->)后聲明它的類型。在 Rust 中,函數(shù)的返回值等同于函數(shù)體最后一個(gè)表達(dá)式的值。這是一個(gè)有返回值的函數(shù)的例子:
fn five() -> i32 {
5
}
fn main() {
let x = five();
println!("The value of x is: {}", x);
}
控制流
Rust 代碼中最常見的用來控制執(zhí)行流的結(jié)構(gòu)是if表達(dá)式和循環(huán)。
- if 表達(dá)式
所有if表達(dá)式以 if 關(guān)鍵字開頭,后跟一個(gè)條件,條件必須是 bool(不需要圓括號(hào))。Rust 并 不會(huì) 自動(dòng)將非布爾值轉(zhuǎn)換為布爾值。
let number = 6;
if number % 3 == 0 {
println!("number is divisible by 3");
} else if number % 2 == 0 {
println!("number is divisible by 2");
} else {
println!("number is not divisible by 4, 3, or 2");
}
使用過多的
else if表達(dá)式會(huì)使代碼顯得雜亂無章,所以如果有多于一個(gè)else if,最好使用match重構(gòu)代碼。由于if是表達(dá)式,所以可以用在let變量聲明中,但是必須注意值的類型一致,必須包含有
else塊,并且別忘了結(jié)尾的分號(hào)。
- 循環(huán)
rust 有三種循環(huán)類型:loop、while、for。
- 無限循環(huán)loop
loop {
println!("again!");
}
可以使用
break關(guān)鍵字來告訴程序何時(shí)停止執(zhí)行循環(huán)。
- 條件循環(huán) while
let mut number = 3;
while number != 0 {
println!("{}!", number);
number = number - 1;
}
println!("LIFTOFF!!!");
- 集合遍歷 for
let a = [10, 20, 30, 40, 50];
for element in a.iter() {
println!("the value is: {}", element);
}
for 循環(huán)的安全性和簡潔性使得它在成為 Rust 中使用最多的循環(huán)結(jié)構(gòu)。即使是在想要循環(huán)執(zhí)行代碼特定次數(shù)時(shí),使用 Range,它是標(biāo)準(zhǔn)庫提供的用來生成從一個(gè)數(shù)字開始到另一個(gè)數(shù)字結(jié)束的所有數(shù)字序列的類型。例如:
for i in 1..10 {
println!("value is:{}",i);
}
還可以使用 rev 方法來反轉(zhuǎn) Range:
for i in (1..10).rev() {
println!("value is:{}",i);
}
所有權(quán)
堆與棧
- 棧: 后進(jìn)先出,增加數(shù)據(jù)叫進(jìn)棧,移除數(shù)據(jù)叫出棧。
操作棧是非常快的,因?yàn)樗L問數(shù)據(jù)的方式:永遠(yuǎn)也不需要尋找一個(gè)位置放入新數(shù)據(jù)或取出數(shù)據(jù),因?yàn)檫@個(gè)位置總是在棧頂。所有數(shù)據(jù)都必須是一個(gè)已知的固定大小。
- 堆:訪問堆上的數(shù)據(jù)要比訪問棧上的數(shù)據(jù)要慢,因?yàn)楸仨毻ㄟ^指針來訪問。
當(dāng)調(diào)用一個(gè)函數(shù),傳遞給函數(shù)的值(包括可能指向堆上數(shù)據(jù)的指針)和函數(shù)的局部變量被壓入棧中。當(dāng)函數(shù)結(jié)束時(shí),這些值被移除棧。
所有權(quán)規(guī)則
- 每一個(gè)值都被它的所有者(owner)變量擁有。
- 值在任意時(shí)刻只能被一個(gè)所有者擁有。
- 當(dāng)所有者離開作用域,這個(gè)值將被丟棄。
String 類型
String 類型存儲(chǔ)在堆上,可以用 from 從字符串字面值來創(chuàng)建 String 如:let s = String::from("hello");
對(duì)于String類型,為了支持一個(gè)可變,可增長的文本片段,需要在堆上分配一塊在編譯時(shí)未知大小的內(nèi)存來存放內(nèi)容。這意味著:
內(nèi)存必須在運(yùn)行時(shí)向操作系統(tǒng)請(qǐng)求。
需要一個(gè)當(dāng)我們處理完
String時(shí)將內(nèi)存返回給操作系統(tǒng)的方法。
Rust 采取了一個(gè)不同的策略:內(nèi)存在擁有它的變量離開作用域后就被自動(dòng)釋放。當(dāng)變量離開作用域,Rust 為其調(diào)用一個(gè)特殊的函數(shù)。這個(gè)函數(shù)叫做 drop。在這里String的作者可以放置釋放內(nèi)存的代碼。Rust 在結(jié)尾的}處自動(dòng)調(diào)用 drop。
String 由三部分組成,如下圖左側(cè)所示:一個(gè)指向存放字符串內(nèi)容內(nèi)存的指針,一個(gè)長度,和一個(gè)容量。這一組數(shù)據(jù)儲(chǔ)存在棧上。右側(cè)則是堆上存放內(nèi)容的內(nèi)存部分。
長度代表當(dāng)前String的內(nèi)容使用了多少字節(jié)的內(nèi)存。容量是String從操作系統(tǒng)總共獲取了多少字節(jié)的內(nèi)存。
拷貝指針、長度和容量而不拷貝數(shù)據(jù)可能聽起來像淺拷貝。在Rust中這個(gè)操作被稱為移動(dòng)(move),而不是淺拷貝,移動(dòng)是指所有權(quán)的轉(zhuǎn)移。Rust 永遠(yuǎn)也不會(huì)自動(dòng)創(chuàng)建數(shù)據(jù)的“深拷貝”。當(dāng) move 發(fā)生的時(shí)候,所有權(quán)被轉(zhuǎn)移的變量,將會(huì)被釋放。
如果我們確實(shí)需要深度復(fù)制String中堆上的數(shù)據(jù),而不僅僅是棧上的數(shù)據(jù),可以使用一個(gè)叫做clone()的通用函數(shù)。
任何簡單標(biāo)量值的組合可以是Copy的,對(duì)于實(shí)現(xiàn)了Copy Trait的類型來說,當(dāng)移動(dòng)發(fā)生的時(shí)候,它們可以Copy的副本代替自己去移動(dòng),而自身還保留著所有權(quán)。
- 所有整數(shù)類型,比如
u32。 - 布爾類型,
bool,它的值是true和false。 - 所有浮點(diǎn)數(shù)類型,比如
f64。 - 元組,當(dāng)且僅當(dāng)其包含的類型也都是Copy的時(shí)候。如:
(i32, i32)是Copy的,不過(i32, String)就不是。
值得注意的是,Rust 不允許自身或其任何部分實(shí)現(xiàn)了Drop trait 的類型使用Copy trait。
將值傳遞給函數(shù)在語言上與給變量賦值相似。向函數(shù)傳遞值可能會(huì)移動(dòng)或者復(fù)制,就像賦值語句一樣。所以如 String 類型,如果將它作為參數(shù)傳遞給函數(shù)后,就會(huì)產(chǎn)生移動(dòng),之后不能在使用。
fn main() {
let s = String::from("hello");
takes_ownership(s); // 傳遞參數(shù)和變量賦值相似 s產(chǎn)生了移動(dòng),所有權(quán)被轉(zhuǎn)移
println!("{}", s); // error: value used here after move
}
fn takes_ownership(some_string: String) {
println!("{}", some_string);
}
變量的所有權(quán)總是遵循相同的模式:將值賦值給另一個(gè)變量時(shí)移動(dòng)它。當(dāng)持有堆中數(shù)據(jù)值的變量離開作用域時(shí),其值將通過drop被清理掉,除非數(shù)據(jù)被移動(dòng)為另一個(gè)變量所有。
作用域
作用域在Rust中的作用就是制造一個(gè)邊界,這個(gè)邊界是所有權(quán)的邊界。變量走出其所在作用域,所有權(quán)會(huì)move。如果不想讓所有權(quán) move ,則可以使用 “引用” 來“出借”變量,而此時(shí)作用域的作用就是保證被“借用”的變量準(zhǔn)確歸還。
在當(dāng)前作用域中由 let 開啟的作用域是隱式作用域。在 Rust 中,也有一些特殊的宏,比如 println!(),也會(huì)產(chǎn)生一個(gè)默認(rèn)的作用域,并且會(huì)隱式借用變量。
引用和借用
使用&符號(hào),就是引用,允許使用其值但不獲取它的所有權(quán)。將獲取引用作為函數(shù)參數(shù)稱為借用。
正如變量默認(rèn)是不可變的,引用也一樣。不允許修改引用的值。
可變引用
fn main() {
let mut s = String::from("hello");
change(&mut s);
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
可變引用有一個(gè)很大的限制:在特定作用域中一個(gè)數(shù)據(jù)有且只有一個(gè)可變引用,也不能在擁有不可變引用的同時(shí)擁有可變引用。這個(gè)限制的好處是 Rust 可以在編譯時(shí)就避免數(shù)據(jù)競爭(data races)。
數(shù)據(jù)競爭是一種特定類型的競爭狀態(tài),它可由這三個(gè)行為造成:
- 兩個(gè)或更多指針同時(shí)訪問同一數(shù)據(jù)。
- 至少有一個(gè)指針被寫入。
- 沒有同步數(shù)據(jù)訪問的機(jī)制。
引用的規(guī)則
- 在任意給定時(shí)間,只能擁有如下中的一個(gè):
- 一個(gè)可變引用。
- 任意數(shù)量的不可變引用。
- 引用必須總是有效的。
Slices
slice數(shù)據(jù)類型,沒有所有權(quán)。它允許引用集合中一段連續(xù)的元素序列,而不用引用整個(gè)集合。
字符串 slice
字符串 slice(string slice)是 String 中一部分值的引用,它看起來像這樣:
let s = String::from("hello world");
let hello = &s[0..5]; // 也可以寫成 &s[..5]
let world = &s[6..11]; // &s[6..]
let hw = &s[..] // 截取整個(gè)字符串
不同于整個(gè)String的引用,這是一個(gè)包含String內(nèi)部的一個(gè)位置和所需元素?cái)?shù)量的引用。
字符串字面值就是 slice 比如:let s = "Hello, world!"; 這里 s 的類型就是 &str:它是一個(gè)指向二進(jìn)制程序特定位置的slice。這也就是為什么字符串字面值是不可變的;&str 是一個(gè)不可變引用。
可以對(duì)其它所有類型的集合使用 slice 例如:let a = [1, 2, 3, 4, 5];let slice = &a[1..3]; 這個(gè) slice 的類型是 &[i32]。
結(jié)構(gòu)體
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
為了從結(jié)構(gòu)體中獲取某個(gè)值,可以使用點(diǎn)號(hào) . 。如果我們只想要用戶的郵箱地址,可以用 user1.email。
在 User 結(jié)構(gòu)體的定義中,我們使用了自身擁有所有權(quán)的String類型而不是 &str 字符串 slice 類型。因?yàn)槲覀兿胍@個(gè)結(jié)構(gòu)體擁有它所有的數(shù)據(jù),為此只要整個(gè)結(jié)構(gòu)體是有效的話其數(shù)據(jù)也應(yīng)該是有效的。
聲明結(jié)構(gòu)體,字段必須要聲明類型,否則會(huì)報(bào)錯(cuò)。
可以使結(jié)構(gòu)體儲(chǔ)存被其它對(duì)象擁有的數(shù)據(jù)的引用,不過這么做的話需要用上 生命周期(lifetimes)。生命周期確保結(jié)構(gòu)體引用的數(shù)據(jù)有效性跟結(jié)構(gòu)體本身保持一致。
方法
方法與函數(shù)類似:使用fn關(guān)鍵字和名字聲明,可以擁有參數(shù)和返回值,同時(shí)包含一些代碼會(huì)在某處被調(diào)用時(shí)執(zhí)行。
與函數(shù)不同的是,它們?cè)诮Y(jié)構(gòu)體(或者枚舉或者 trait 對(duì)象)的上下文中被定義,并且它們第一個(gè)參數(shù)總是 self,它代表方法被調(diào)用的結(jié)構(gòu)體的實(shí)例。
struct Rectangle {
length: u32,
width: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.length * self.width
}
}
可以在 impl 塊中定義不以 self 作為參數(shù)的函數(shù),稱為關(guān)聯(lián)函數(shù),因?yàn)樗鼈兒徒Y(jié)構(gòu)體相關(guān)聯(lián)。即便如此它們也是函數(shù)而不是方法,因?yàn)樗鼈儾⒉蛔饔糜谝粋€(gè)結(jié)構(gòu)體的實(shí)例。例如:String::from 就是一個(gè)關(guān)聯(lián)函數(shù)。
關(guān)聯(lián)函數(shù)經(jīng)常被用作返回一個(gè)結(jié)構(gòu)體新實(shí)例的構(gòu)造函數(shù)。
impl Rectangle {
fn square(size: u32) -> Rectangle {
Rectangle { length: size, width: size }
}
}
使用結(jié)構(gòu)體名和 :: 語法來調(diào)用這個(gè)關(guān)聯(lián)函數(shù):比如 let sq = Rectangle::square(3); 。這個(gè)方法位于結(jié)構(gòu)體的命名空間中: :: 語法用于關(guān)聯(lián)函數(shù)和模塊創(chuàng)建的命名空間。
枚舉
使用 enum 關(guān)鍵字聲明枚舉,可以將任意類型的數(shù)據(jù)放入枚舉成員中:例如字符串、數(shù)字類型或者結(jié)構(gòu)體。甚至可以包含另一個(gè)枚舉!
枚舉同結(jié)構(gòu)體一樣使用 impl 聲明其方法,方法體使用了 self 來調(diào)用方法的值。
Option 枚舉
Rust 并沒有很多其它語言中有的空值功能。不過它確實(shí)擁有一個(gè)可以編碼存在或不存在概念的枚舉。這個(gè)枚舉是 Option<T>,而且它定義于標(biāo)準(zhǔn)庫中,如下:
enum Option<T> {
Some(T),
None,
}
空值(Null) 是一個(gè)值,它代表沒有值。在有空值的語言中,變量總是這兩種狀態(tài)之一:空值和非空值。
Option<T> 已被 prelude 自動(dòng)引用,不需要顯式導(dǎo)入它,它的成員也是如此,不需要 Option::前綴來直接使用Some和None。即便如此 Option<T> 也仍是常規(guī)的枚舉,Some(T)和None仍是Option<T>的成員。
如果使用 None 而不是 Some,需要告訴 Rust Option<T>是什么類型的,因?yàn)榫幾g器只通過None值無法推斷出Some成員的類型。
let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None; // 這里必須聲明類型
當(dāng)有一個(gè) Some 值時(shí),我們就知道存在一個(gè)值,而這個(gè)值保存在 Some 中。當(dāng)有個(gè) None 值時(shí),在某種意義上它跟空值是相同的意義:并沒有一個(gè)有效的值。
Option<T> 和 T(這里T可以是任何類型)是不同的類型,編譯器不允許像一個(gè)被定義的有效的類型那樣使用 Option<T>。例如,這些代碼不能編譯,因?yàn)樗鼑L試將 Option<i8>與i8相比:
let x: i8 = 5;
let y: Option<i8> = Some(5);
let sum = x + y; // error no implementation for `i8 + Option<i8>`
當(dāng)在 Rust 中擁有一個(gè)像i8這樣類型的值時(shí),編譯器確保它總是有一個(gè)有效的值。我們可以自信使用而無需判空。只有當(dāng)使用Option<i8>(或者任何用到的類型)是需要擔(dān)心可能沒有一個(gè)值,而編譯器會(huì)確保我們?cè)谑褂弥抵疤幚頌榭盏那闆r。
match 運(yùn)算符
match 允許將一個(gè)值與一系列的模式相比較并根據(jù)匹配的模式執(zhí)行代碼。模式可由字面值、變量、通配符和許多其它內(nèi)容構(gòu)成。
match 關(guān)鍵字后跟一個(gè)任意類型的表達(dá)式,之后是 match 分支:一個(gè)模式和一些代碼。使用 => 運(yùn)算符將模式和將要運(yùn)行的代碼分開。每個(gè)分支相關(guān)聯(lián)的代碼是一個(gè)表達(dá)式,而表達(dá)式的結(jié)果值將作為整個(gè) match 表達(dá)式的返回值。例如:
match coin {
Coin::Penny => {
println!("Lucky penny!");
1
},
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
Rust 中的匹配是窮盡的:必須窮舉到最后的可能性來使代碼有效。
_ 通配符
_ 模式會(huì)匹配所有的值。通過將其放置于其它分支之后,將會(huì)匹配所有之前沒有指定的可能的值。如果希望匹配后不做任何處理可以這樣 _ =>(),
if let
if let是match的一個(gè)語法糖,它當(dāng)只匹配某一模式時(shí)執(zhí)行代碼而忽略所有其它值。但這樣會(huì)失去match強(qiáng)制要求的窮盡性檢查。
if let Some(3) = some_u8_value {
println!("three");
}
可以在if let中包含一個(gè)else。else塊中的代碼與match表達(dá)式中的_分支塊中的代碼相同。
let mut count = 0;
if let Coin::Quarter(state) = coin {
println!("State quarter from {:?}!", state);
} else {
count += 1;
}
模塊
模塊(module)是一個(gè)包含函數(shù)或類型定義的命名空間,你可以選擇這些定義是能(公有)還是不能(私有)在其模塊外可見。一個(gè)模塊按照如下工作:
- 使用
mod關(guān)鍵字聲明模塊 - 默認(rèn)所有內(nèi)容都是私有的(包括模塊自身)??梢允褂?code>pub關(guān)鍵字將其變成公有并在其命名空間外可見。
-
use關(guān)鍵字允許引入模塊、或模塊中的定義到作用域中以便于引用它們。
模塊文件系統(tǒng)的規(guī)則
- 如果一個(gè)叫做
foo的模塊沒有子模塊,應(yīng)該將foo的聲明放入叫做foo.rs的文件中。 - 如果一個(gè)叫做
foo的模塊有子模塊,應(yīng)該將foo的聲明放入叫做foo/mod.rs的文件中。模塊自身則應(yīng)該使用mod關(guān)鍵字定義于父模塊的文件中。
可見性規(guī)則
- 如果一個(gè)模塊是公有的,它能被任何父模塊訪問。
- 如果一個(gè)模塊是私有的,它只能被當(dāng)前模塊或其子模塊訪問。
- 同位于根模塊,私有模塊的公有函數(shù)可以被訪問,私有模塊和私有函數(shù)都不可被訪問。
- 除上述以外,私有模塊的公有函數(shù),公有模塊的私有函數(shù)都不可被訪問。
我們創(chuàng)建的所有模塊都位于一個(gè)與 crate 同名的模塊內(nèi)部。這個(gè)頂層的模塊被稱為 crate 的根模塊(root module)。
另外注意到即便在項(xiàng)目的子模塊中使用外部 crate,extern crate也應(yīng)該位于根模塊(也就是 src/main.rs 或 src/lib.rs)。接著,在子模塊中,我們就可以像頂層模塊那樣引用外部 crate 中的項(xiàng)了。
使用 use 簡化導(dǎo)入
use 關(guān)鍵字的工作是縮短冗長的函數(shù)調(diào)用,通過將想要調(diào)用的函數(shù)所在的模塊引入到作用域中。例如:
pub mod a {
pub mod series {
pub mod of {
pub fn nested_modules() {}
}
}
}
use a::series::of; // 每當(dāng)想要引用of模塊時(shí),不用使用完整的a::series::of路徑,直接使用of
fn main() {
of::nested_modules();
}
use 關(guān)鍵字只將指定的模塊引入作用域;它并不會(huì)將其子模塊也引入。
可以將函數(shù)本身引入到作用域中,通過如下在use中指定函數(shù)的方式:
use a::series::of::nested_modules;
fn main() {
nested_modules();
}
因?yàn)槊杜e也像模塊一樣組成了某種命名空間,也可以使用use來導(dǎo)入枚舉的成員。對(duì)于任何類型的use語句,如果從一個(gè)命名空間導(dǎo)入多個(gè)項(xiàng),可以使用大括號(hào)和逗號(hào)來列舉它們,像這樣:
enum TrafficLight {
Red,
Yellow,
Green,
}
use TrafficLight::{Red, Yellow};
fn main() {
let red = Red;
let yellow = Yellow;
let green = TrafficLight::Green;
}
為了一次導(dǎo)入某個(gè)命名空間的所有項(xiàng),可以使用 * 語法。例如:
enum TrafficLight {
Red,
Yellow,
Green,
}
use TrafficLight::*;
fn main() {
let red = Red;
let yellow = Yellow;
let green = Green;
}
*被稱為 全局導(dǎo)入(glob),它會(huì)導(dǎo)入命名空間中所有可見的項(xiàng)。全局導(dǎo)入應(yīng)該保守的使用:它們是方便的,但是也可能會(huì)引入多于你預(yù)期的內(nèi)容從而導(dǎo)致命名沖突。
使用 super 訪問父模塊
使用 super 關(guān)鍵字獲取當(dāng)前模塊的父模塊,如果是用::mod1::mod2 則要從根模塊開始列出整個(gè)路徑。
use 關(guān)鍵字是相對(duì)于根模塊開始的,可以通過 use super:child1 相對(duì)于父模塊開始。
通用集合類型
不同于內(nèi)建的數(shù)組和元組類型,這些集合指向的數(shù)據(jù)是儲(chǔ)存在堆上的,這意味著數(shù)據(jù)的數(shù)量不必在編譯時(shí)就可知并且可以隨著程序的運(yùn)行增長或縮小。
vector
Vec<T> 類型,也被稱為 vector。允許我們?cè)谝粋€(gè)單獨(dú)的數(shù)據(jù)結(jié)構(gòu)中儲(chǔ)存多于一個(gè)值,它在內(nèi)存中彼此相鄰的排列所有的值。vector 只能儲(chǔ)存相同類型的值。
使用 Vec::new 函數(shù)創(chuàng)建空的 vector:let v: Vec<i32> = Vec::new(); 由于沒有向這個(gè) vector 中插入任何值,Rust 并不知道我們想要儲(chǔ)存什么類型的元素。所以需要添加類型注解。
更常見的做法是使用初始值來創(chuàng)建一個(gè) vector,這樣可以省去類型注解。為了方便,Rust 提供了 vec! 宏。它會(huì)根據(jù)提供的值來創(chuàng)建新的vector,例如:let v = vec![1,2,3];
可以使用 push方法向vector添加元素,但必須聲明是 mut:
let mut v = vec![];
v.push(1);
在 vector 的結(jié)尾增加新元素時(shí),在沒有足夠空間將所有所有元素依次相鄰存放的情況下,可能會(huì)要求分配新內(nèi)存并將舊的元素拷貝到新的空間中。這時(shí),對(duì)之前元素的引用就指向了被釋放的內(nèi)存,會(huì)引發(fā)錯(cuò)誤,例如:
let mut v = vec![1, 2, 3, 4, 5];
let first = &v[0]; // 這里的引用會(huì)引發(fā)錯(cuò)誤,直接用 v[0] 則不會(huì)。
v.push(6);
還可以是用 pop 方法移除并返回 vector 的最后一個(gè)元素。
vector 在其離開作用域時(shí)會(huì)被釋放,其內(nèi)容也會(huì)被丟棄。
讀取 vector 中的元素可以使用索引或get方法,如:
let v = vec![1, 2, 3, 4, 5];
let third: &i32 = &v[2];
let third: Option<&i32> = v.get(2);
使用索引不能訪問不存在的元素,否則會(huì)造成 panic!,而使用get方法訪問不存在的元素會(huì)返回None,因?yàn)樗祷氐氖?code>Option<T>類型,要么是Some(T)要么是None。
由于 vector 只能存儲(chǔ)相同類型的值,需要存儲(chǔ)不同類型的值時(shí),可以使用枚舉,因?yàn)槊杜e的成員都是枚舉類型。例如:
enum SpreadsheetCell {
Int(i32),
Float(f64),
Text(String),
}
let row = vec![
SpreadsheetCell::Int(3),
SpreadsheetCell::Text(String::from("blue")),
SpreadsheetCell::Float(10.12),
];
字符串
Rust 核心語言中事實(shí)上只有一種字符串類型:str 字符串 slice,它通常以被借用的形式出現(xiàn)——&str。它們是一些存儲(chǔ)在別處的 UTF-8 編碼字符串?dāng)?shù)據(jù)的引用。比如字符串字面值被存儲(chǔ)在程序的二進(jìn)制輸出中,字符串 slice 也是如此。
稱作 String 的類型是由標(biāo)準(zhǔn)庫提供的,而沒有寫進(jìn)核心語言部分, 它是可增長的、可變的、有所有權(quán)的、UTF-8編碼的字符串類型。當(dāng)談到 Rust 的“字符串”時(shí),它們通常指的是 String 和字符串 slice &str 類型,而不是其中一個(gè)。
String和字符串 slice 都是 UTF-8編碼的。
Rust 標(biāo)準(zhǔn)庫中還包含一系列其他字符串類型,比如 OsString、OsStr、CString和CStr。
新建字符串
使用 new()函數(shù)創(chuàng)建空字符串。let s = String::new();
可以使用 to_string方法創(chuàng)建有內(nèi)容的字符串。它能用于任何實(shí)現(xiàn)了 Display trait 的類型,對(duì)于字符串字面值:
let data = "initial contents";
let s = data.to_string();
// 也可以直接對(duì)字符串字面值使用
let s = "initial contents".to_string();
還可以使用 String::from() 函數(shù)來從字符串字面值創(chuàng)建 String。等同于使用 to_string:let s = String::from("initial contents");
更新字符串
String 的大小可以增長其內(nèi)容也可以改變。
- 使用
push_str方法添加字符串 slice。
let mut s = String::from("foo");
let s2 = String::from("bar");
s.push_str("bar");
s.push_str(&s2) // 這里是&s2 而不是s2
- 使用
push方法添加字符。
let mut s = String::from("lo");
s.push('l');
- 使用
+運(yùn)算符將兩個(gè)已知的字符串合并在一起,只能將&str和String相加,不能將兩個(gè)String值或兩個(gè)&str相加。
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
// let s3 = s1 + "wold";
let s3 = s1 + &s2; // s1 會(huì)發(fā)生轉(zhuǎn)移 之后將不能使用
- 使用
format!宏,將多個(gè)String或&str合并在一起。
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
let s = format!("{}-{}-{}", s1, s2, s3);
索引字符串
Rust 的字符串 不支持索引。因?yàn)?String 是一個(gè) Vec<u8> 的封裝。采用 UTF-8 編碼,不同的語言文字,一個(gè)字符串字節(jié)值的索引并不總是對(duì)應(yīng)一個(gè)有效的 Unicode 標(biāo)量值。Rust不得不檢查從字符串的開頭到索引位置的內(nèi)容來確定這里有多少有效的字符,這就損害了性能,同時(shí),為了避免返回意想不到的值造成意外的bug,Rust禁止使用索引。但如果確實(shí)需要的話可以使用字符串 slice。例如:
let s1 = String::from("tic");
let s2= "wold";
println!("{}", &s1[..1],&s2[1..2]); // 不要忘了 & 符號(hào)
遍歷字符串
-
chars方法,操作單獨(dú)的 Unicode 標(biāo)量值。
for c in "??????".chars() {
println!("{}", c); // ? ? ? ? ? ?
}
-
bytes方法返回每一個(gè)原始字節(jié)。
for b in "??????".bytes() {
println!("{}", b); // 224 164 168 224 ...
}
HashMap
HashMap<K,V> 類型儲(chǔ)存了一個(gè)鍵類型 K 對(duì)應(yīng)一個(gè)值類型 V 的映射。它通過一個(gè) 哈希函數(shù)(hashing function)來實(shí)現(xiàn)映射,決定如何將鍵和值放入內(nèi)存中。
- 使用
new創(chuàng)建一個(gè)空的 HashMap,并使用insert來增加元素。
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
HashMap 是同質(zhì)的:所有的鍵必須是相同類型,值也必須都是相同類型。
- 使用
collect方法,結(jié)合zip方法,將 元祖 vector 轉(zhuǎn)換成 HashMap。
use std::collections::HashMap;
let teams = vec![String::from("Blue"), String::from("Yellow")];
let initial_scores = vec![10, 50];
let scores: HashMap<_, _> = teams.iter().zip(initial_scores.iter()).collect(); // HashMap<_, _>類型注解是必要的
HashMap 和所有權(quán)
use std::collections::HashMap;
let field_name = String::from("Favorite color");
let field_value = String::from("Blue");
let mut map = HashMap::new();
map.insert(field_name, field_value); // field_name field_value 被插入后所有權(quán)轉(zhuǎn)移到 map,之后不能使用這兩個(gè)綁定
// 如果將值的引用插入哈希 map,這些值本身將不會(huì)被移動(dòng)進(jìn)哈希 map。但是這些引用指向的值必須至少在哈希 map 有效時(shí)也是有效的
訪問 HashMap 中的值
- 通過
get方法并提供對(duì)應(yīng)的鍵來從 HashMap 中獲取值:
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
let team_name = String::from("Blue");
let score = scores.get(&team_name); // 返回的是 Option<T>類型
- 使用與
for循環(huán)遍歷 HashMap 中的每一個(gè)鍵值對(duì):
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
for (key, value) in &scores {
println!("{}: {}", key, value); // 以任意順序打印出每一個(gè)鍵值對(duì):
}
更新 HashMap
- 使用
insert插入一個(gè)鍵值對(duì),如果之前有值,新值會(huì)代替舊值:
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Blue"), 25);
println!("{:?}", scores); // {"Blue": 25}
- 使用
entry和or_insert方法,如果存在就忽略,如果不存在就插入,or_insert方法會(huì)返回這個(gè)鍵的值的一個(gè)可變引用(&mut V):
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.entry(String::from("Yellow")).or_insert(50);
scores.entry(String::from("Blue")).or_insert(50); // Blue 已存在 不會(huì)改變
println!("{:?}", scores);
- 根據(jù)舊值更新一個(gè)值:
use std::collections::HashMap;
let text = "hello world wonderful world";
let mut map = HashMap::new();
for word in text.split_whitespace() {
let count = map.entry(word).or_insert(0);
*count += 1;
}
println!("{:?}", map); // {"world": 2, "hello": 1, "wonderful": 1}
錯(cuò)誤處理
Rust 將錯(cuò)誤組合成兩個(gè)主要類別:可恢復(fù)錯(cuò)誤(recoverable)和 不可恢復(fù)錯(cuò)誤(unrecoverable)。可恢復(fù)錯(cuò)誤 通常代表向用戶報(bào)告錯(cuò)誤和重試操作是合理的情況,比如未找到文件。不可恢復(fù)錯(cuò)誤 通常是 bug 的同義詞,比如嘗試訪問超過數(shù)組結(jié)尾的位置。
Rust 并沒有異常。對(duì)于可恢復(fù)錯(cuò)誤有 Result<T, E> 值,對(duì)于不可恢復(fù)錯(cuò)誤有 panic!。
panic!宏
當(dāng)執(zhí)行這個(gè)宏時(shí),程序會(huì)打印出一個(gè)錯(cuò)誤信息,展開并清理?xiàng)?shù)據(jù),然后接著退出。出現(xiàn)這種情況的場景通常是檢測到一些類型的 bug 而且程序員并不清楚該如何處理它。
當(dāng)出現(xiàn)
panic!時(shí),程序默認(rèn)開始 展開(unwinding),Rust 會(huì)回溯棧并清理它遇到的每一個(gè)函數(shù)的數(shù)據(jù),不過這個(gè)回溯并清理的過程有很多工作。如果需要最終發(fā)布的二進(jìn)制文件越小越好,可以選擇:直接 終止(abort) ——不清理數(shù)據(jù)就退出程序。那么程序所使用的內(nèi)存需要由操作系統(tǒng)來清理。通過在 Cargo.toml 的[profile]部分增加panic = 'abort',將展開切換為終止。例如:[profile.release] panic = 'abort'
- 主動(dòng)調(diào)用
panic!
fn main() {
panic!("crash and burn");
}
運(yùn)行程序?qū)?huì)出現(xiàn)類似這樣的輸出:
Compiling panic v0.1.0 (file:///projects/panic)
Finished debug [unoptimized + debuginfo] target(s) in 0.25 secs
Running `target/debug/panic`
thread 'main' panicked at 'crash and burn', src/main.rs:2 // 顯示錯(cuò)誤信息及出現(xiàn)位置
note: Run with `RUST_BACKTRACE=1` for a backtrace.
error: Process didn't exit successfully: `target/debug/panic.exe` (exit code: 101)
- 因?yàn)榇a中的 bug 引起的別的庫中
panic!
fn main() {
let v = vec![1, 2, 3];
v[100];
}
運(yùn)行程序會(huì)出現(xiàn)如下錯(cuò)誤信息:
Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
Running `target\debug\panic.exe`
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 11', C:\projects\rust\src\libcollections\vec.rs:1552 // 顯示別人代碼中 錯(cuò)誤信息及位置
note: Run with `RUST_BACKTRACE=1` for a backtrace.
error: process didn't exit successfully: `target\debug\panic.exe` (exit code: 101)
上述情況可以設(shè)置 RUST_BACKTRACE=1來顯示更多信息:
- cmd下,輸入
set RUST_BACKTRACE=1 - power shell下,輸入
$env:RUST_BACKTRACE=1
之后再執(zhí)行 cargo run,顯示如下信息:
Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
Running `target\debug\panic.exe`
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 11', C:\projects\rust\src\libcollections\vec.rs:1552
stack backtrace:
0: std::sys_common::backtrace::_print
at C:\projects\rust\src\libstd\sys_common\backtrace.rs:71
1: std::panicking::default_hook::{{closure}}
at C:\projects\rust\src\libstd\panicking.rs:354
2: std::panicking::default_hook
at C:\projects\rust\src\libstd\panicking.rs:371
3: std::panicking::rust_panic_with_hook
at C:\projects\rust\src\libstd\panicking.rs:549
4: std::panicking::begin_panic<collections::string::String>
at C:\projects\rust\src\libstd\panicking.rs:511
5: std::panicking::begin_panic_fmt
at C:\projects\rust\src\libstd\panicking.rs:495
6: std::panicking::rust_begin_panic
at C:\projects\rust\src\libstd\panicking.rs:471
7: core::panicking::panic_fmt
at C:\projects\rust\src\libcore\panicking.rs:69
8: core::panicking::panic_bounds_check
at C:\projects\rust\src\libcore\panicking.rs:56
9: collections::vec::{{impl}}::index<i32>
at C:\projects\rust\src\libcollections\vec.rs:1552
10: panic::main
at .\src\main.rs:3 // 自己程序中引起錯(cuò)誤的行
11: panic_unwind::__rust_maybe_catch_panic
at C:\projects\rust\src\libpanic_unwind\lib.rs:98
12: std::rt::lang_start
at C:\projects\rust\src\libstd\rt.rs:52
13: main
14: __scrt_common_main_seh
at f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl:259
15: BaseThreadInitThunk
error: process didn't exit successfully: `target\debug\panic.exe` (exit code: 101)
上面顯示了程序執(zhí)行到目前位置所有被調(diào)用的函數(shù)的列表。從頭開始讀直到發(fā)現(xiàn)自己的文件。這就是問題的發(fā)源地。這一行往上是調(diào)用的代碼;往下則是被調(diào)用的代碼。這些行可能包含核心 Rust 代碼,標(biāo)準(zhǔn)庫代碼或用到的 crate 代碼。
Result
enum Result<T, E> {
Ok(T),
Err(E),
}
T 和 E 是泛型類型參數(shù): T 代表成功時(shí)返回的
Ok 成員中的數(shù)據(jù)的類型,而 E 代表失敗時(shí)返回的 Err 成員中的錯(cuò)誤的類型。
use std::fs::File;
use std::io::ErrorKind;
fn main() {
let f = File::open("hello.txt"); // 成功時(shí),f的值是一個(gè)包含文件句柄的Ok實(shí)例;
// 失敗時(shí),f會(huì)是一個(gè)包含更多關(guān)于出現(xiàn)了何種錯(cuò)誤信息的Err實(shí)例
let f = match f {
Ok(file) => file,
// 如果因?yàn)槲募淮嬖诙?,?chuàng)建文件并返回新文件的句柄
// if error.kind() == ErrorKind::NotFound被稱作 match guard:
// 它是一個(gè)進(jìn)一步完善match分支模式的額外的條件。這個(gè)條件必須為真才能使分支的代碼被執(zhí)行;
// 否則,模式匹配會(huì)繼續(xù)并考慮match中的下一個(gè)分支。
// 模式中的ref是必須的,這樣error就不會(huì)被移動(dòng)到 guard 條件中而只是僅僅引用它
// 在模式的上下文中,&匹配一個(gè)引用并返回它的值,而ref匹配一個(gè)值并返回一個(gè)引用。
Err(ref error) if error.kind() == ErrorKind::NotFound => match File::create("hello.txt") {
Ok(fc) => fc,
Err(e) => panic!("Tried to create file but there was a problem: {:?}", e),
},
Err(error) => panic!("There was a problem opening the file: {:?}", error),
};
}
File::open 函數(shù)的返回值類型是 Result<T, E>,成功值的類型 std::fs::File,它是一個(gè)文件句柄;失敗值的類型是 std::io::Error,它是標(biāo)準(zhǔn)庫中提供的結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體有一個(gè)返回 io::ErrorKind 值的 kind 方法可供調(diào)用。io::ErrorKind 是一個(gè)標(biāo)準(zhǔn)庫提供的枚舉,它的成員對(duì)應(yīng) io 操作可能導(dǎo)致的不同錯(cuò)誤類型。ErrorKind::NotFound 代表嘗試打開的文件不存在。
Result枚舉和其成員也被導(dǎo)入到了 prelude 中,所以就不需要Ok和Err之前指定Result::。
失敗時(shí) panic 的捷徑:unwrap和expect
Result<T, E> 類型定義了很多輔助方法來處理各種情況。
-
unwrap: 如果Result值是Ok,返回Ok中的值,如果是Err會(huì)自動(dòng)調(diào)用panic!。
use std::fs::File;
fn main() {
let f = File::open("hello.txt").unwrap();
}
-
expect: 可以在參數(shù)中自定義要顯示的錯(cuò)誤信息,并在自動(dòng)調(diào)用panic!時(shí),顯示在錯(cuò)誤信息中,有助于追蹤 panic 的根源。
use std::fs::File;
fn main() {
let f = File::open("hello.txt").expect("Failed to open hello.txt");
}
傳播錯(cuò)誤
當(dāng)編寫一個(gè)其實(shí)現(xiàn)會(huì)調(diào)用一些可能會(huì)失敗的操作的函數(shù)時(shí),除了在這個(gè)函數(shù)中處理錯(cuò)誤外,還可以選擇讓調(diào)用者知道這個(gè)錯(cuò)誤并決定該如何處理,這被稱為 傳播(propagating)錯(cuò)誤。簡便的專用語法:?,可以在 ? 之后直接使用鏈?zhǔn)椒椒ㄕ{(diào)用來進(jìn)一步縮短代碼:
use std::io;
use std::io::Read;
use std::fs::File;
fn read_username_from_file() -> Result<String, io::Error> {
let mut s = String::new();
File::open("hello.txt")?.read_to_string(&mut s)?;
Ok(s)
}
?只能用于返回值類型為Result的函數(shù)。
示例、代碼原型和測試:非常適合 panic
當(dāng)編寫一個(gè)示例來展示一些概念時(shí),在擁有健壯的錯(cuò)誤處理代碼的同時(shí)也會(huì)使得例子不那么明確。例如,調(diào)用一個(gè)類似 unwrap 這樣可能 panic! 的方法可以被理解為一個(gè)你實(shí)際希望程序處理錯(cuò)誤方式的占位符,它根據(jù)其余代碼運(yùn)行方式可能會(huì)各不相同。
unwrap 和 expect 方法在原型設(shè)計(jì)時(shí)非常方便,在決定該如何處理錯(cuò)誤之前。它們?cè)诖a中留下了明顯的記號(hào),以便準(zhǔn)備使程序變得更健壯時(shí)作為參考。
如果方法調(diào)用在測試中失敗了,我們希望這個(gè)測試都失敗,即便這個(gè)方法并不是需要測試的功能。因?yàn)?panic! 是測試如何被標(biāo)記為失敗的,調(diào)用 unwrap或 expect 都是非常有道理的。
泛型
Rust 通過在編譯時(shí)進(jìn)行泛型代碼的 單態(tài)化(monomorphization)來保證效率。單態(tài)化是一個(gè)將泛型代碼轉(zhuǎn)變?yōu)閷?shí)際放入的具體類型的特定代碼的過程。這意味著在使用泛型時(shí)沒有運(yùn)行時(shí)開銷;當(dāng)代碼運(yùn)行,它的執(zhí)行效率就跟好像手寫每個(gè)具體定義的重復(fù)代碼一樣。
在函數(shù)定義中使用泛型
定義函數(shù)時(shí)可以在函數(shù)簽名的參數(shù)數(shù)據(jù)類型和返回值中使用泛型。
fn largest<T>(list: &[T]) -> T {}
結(jié)構(gòu)體定義中的泛型
使用 <> 來定義擁有一個(gè)或多個(gè)泛型參數(shù)類型字段的結(jié)構(gòu)體。
struct Point<T, U> {
x: T,
y: U,
}
枚舉定義中的泛型數(shù)據(jù)類型
enum Result<T, E> {
Ok(T),
Err(E),
}
方法定義中的枚舉數(shù)據(jù)類型
struct Point<T> {
x: T,
y: T,
}
impl<T> Point<T> {
fn x(&self) -> &T {
&self.x
}
}
注意必須在
impl后面聲明T,這樣就可以在Point<T>上實(shí)現(xiàn)的方法中使用它了。
結(jié)構(gòu)體定義中的泛型類型參數(shù)并不總是與結(jié)構(gòu)體方法簽名中使用的泛型是同一類型。
struct Point<T, U> {
x: T,
y: U,
}
impl<T, U> Point<T, U> {
fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
Point {
x: self.x,
y: other.y,
}
}
}
注意泛型參數(shù)
T和U聲明于impl之后,因?yàn)樗麄冇诮Y(jié)構(gòu)體定義相對(duì)應(yīng)。而泛型參數(shù)V和W聲明于fn mixup之后,因?yàn)樗麄冎皇窍鄬?duì)于方法本身的。
trait
使用 trait 關(guān)鍵字來定義一個(gè) trait,后面是 trait 的名字。在大括號(hào)中聲明描述實(shí)現(xiàn)這個(gè) trait 的類型所需要的行為的方法簽名。在方法簽名后跟分號(hào)而不是在大括號(hào)中提供其實(shí)現(xiàn)。接著每一個(gè)實(shí)現(xiàn)這個(gè) trait 的類型都需要提供其自定義行為的方法體,編譯器也會(huì)確保任何實(shí)現(xiàn)這個(gè) trait 的類型都擁有與這個(gè)簽名的定義完全一致的方法。
trait 體中可以有多個(gè)方法,一行一個(gè)方法簽名且都以分號(hào)結(jié)尾。
pub trait Summarizable {
fn summary(&self) -> String;
}
實(shí)現(xiàn) trait
pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summarizable for Tweet {
fn summary(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
如果要實(shí)現(xiàn)外部定義的 trait 需要先將其導(dǎo)入作用域。不允許對(duì)外部類型實(shí)現(xiàn)外部 trait,可以對(duì)外部類型實(shí)現(xiàn)自定義的 trait,也可以對(duì)自定義類型上實(shí)現(xiàn)外部 trait。這個(gè)限制稱為 孤兒規(guī)則(orphan rule),即其父類型不存在。
默認(rèn)實(shí)現(xiàn)
有時(shí)為 trait 中的某些或全部提供默認(rèn)的行為,而不是在每個(gè)類型的每個(gè)實(shí)現(xiàn)中都定義自己的行為是很有用的。這樣當(dāng)為某個(gè)特定類型實(shí)現(xiàn) trait 時(shí),可以選擇保留或重載每個(gè)方法的默認(rèn)行為。
pub trait Summarizable {
fn summary(&self) -> String {
String::from("(Read more...)")
}
}
使用默認(rèn)實(shí)現(xiàn),可以指定一個(gè)空的 impl 塊:impl Summarizable for NewsArticle {}
重載實(shí)現(xiàn)
重載一個(gè)默認(rèn)實(shí)現(xiàn)的語法與實(shí)現(xiàn)沒有默認(rèn)實(shí)現(xiàn)的 trait 方法時(shí)完全一樣的。默認(rèn)實(shí)現(xiàn)允許調(diào)用相同 trait 中的其他方法,哪怕這些方法沒有默認(rèn)實(shí)現(xiàn)。通過這種方法,trait 可以實(shí)現(xiàn)很多有用的功能而只需實(shí)現(xiàn)一小部分特定內(nèi)容。
pub trait Summarizable {
fn author_summary(&self) -> String;
fn summary(&self) -> String {
format!("(Read more from {}...)", self.author_summary())
}
}
impl Summarizable for Tweet {
fn author_summary(&self) -> String {
format!("@{}", self.username)
}
}
注意在重載過的實(shí)現(xiàn)中調(diào)用默認(rèn)實(shí)現(xiàn)是不可能的。
trait bounds
可以對(duì)泛型類型參數(shù)使用 trait,從而限制泛型不再適用于任何類型,編譯器會(huì)確保其被限制為那些實(shí)現(xiàn)了特定 trait 的類型,由此泛型就會(huì)擁有我們希望其類型所擁有的功能。這被稱為指定泛型的 trait bounds。
pub fn notify<T: Summarizable>(item: T) {
println!("Breaking news! {}", item.summary());
}
可以通過 + 來為泛型指定多個(gè) trait bounds: <T: Summarizable + Display>。在函數(shù)名和參數(shù)列表之間的尖括號(hào)中指定很多的 trait bound 信息將是難以閱讀的,所以將其移動(dòng)到函數(shù)簽名后的 where 從句中。
fn some_function<T, U>(t: T, u: U) -> i32
where T: Display + Clone,
U: Clone + Debug
{}
生命周期與引用有效性
Rust 中的每一個(gè) 引用 都有其生命周期,也就是引用保持有效的作用域。生命周期的主要目標(biāo)是避免懸垂引用,它會(huì)導(dǎo)致程序引用了并非其期望引用的數(shù)據(jù)。
未初始化變量不能被使用
借用檢查器 用來比較作用域來確保所有的借用都是有效的。
生命周期注解語法
生命周期注解并不改變?nèi)魏我玫纳芷诘拈L短。與當(dāng)函數(shù)簽名中指定了泛型類型參數(shù)后就可以接受任何類型一樣,當(dāng)指定了泛型生命周期后函數(shù)也能接受任何生命周期的引用。生命周期注解所做的就是將多個(gè)引用的生命周期聯(lián)系起來。
生命周期參數(shù)名稱必須以單引號(hào)號(hào)(')開頭。生命周期參數(shù)的名稱通常全是小寫,而且類似于泛型類型,其名稱通常非常短。'a 是大多數(shù)人默認(rèn)使用的名稱。生命周期參數(shù)注解位于引用的 & 之后,并有一個(gè)空格來將引用類型與生命周期注解分隔開。
&i32 // 沒有生命周期的引用
&'a i32 // 有生命周期的引用
&'a mut i32 // 有生命周期的可變引用
生命周期注解告訴 Rust 多個(gè)引用的泛型生命周期參數(shù)如何相互聯(lián)系。如果函數(shù)有一個(gè)生命周期 'a 的 i32 的引用的參數(shù) first,還有另一個(gè)同樣是生命周期 'a 的 i32 的引用的參數(shù) second,這兩個(gè)生命周期注解有相同的名稱意味著 first 和 second 必須與這相同的泛型生命周期存在得一樣久。
函數(shù)簽名中的生命周期注解
泛型生命周期參數(shù)需要聲明在函數(shù)名和參數(shù)列表間的尖括號(hào)中。
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
通過在函數(shù)簽名中指定生命周期參數(shù),不會(huì)改變?nèi)魏螀?shù)或返回值的生命周期,任何不堅(jiān)持這個(gè)協(xié)議的類型都將被借用檢查器拒絕。
當(dāng)從函數(shù)返回一個(gè)引用,返回值的生命周期參數(shù)需要與一個(gè)參數(shù)的生命周期參數(shù)相匹配。如果返回的引用沒有指向任何一個(gè)參數(shù),那么唯一的可能就是它指向一個(gè)函數(shù)內(nèi)部創(chuàng)建的值,它將會(huì)是一個(gè)懸垂引用,因?yàn)樗鼘?huì)在函數(shù)結(jié)束時(shí)離開作用域。
生命周期語法是關(guān)于如何聯(lián)系函數(shù)不同參數(shù)和返回值的生命周期的。一旦他們形成了某種聯(lián)系,Rust 就有了足夠的信息來允許內(nèi)存安全的操作并阻止會(huì)產(chǎn)生懸垂指針亦或是違反內(nèi)存安全的行為。
結(jié)構(gòu)體定義中的生命周期注解
可以定義存放引用的結(jié)構(gòu)體,但需要為結(jié)構(gòu)體定義中的每一個(gè)引用添加生命周期注解。
struct ImportantExcerpt<'a> {
part: &'a str,
}
生命周期省略
函數(shù)或方法的參數(shù)的生命周期被稱為 輸入生命周期(input lifetimes),而返回值的生命周期被稱為 輸出生命周期(output lifetimes)。
譯器用于判斷引用何時(shí)不需要明確生命周期注解的規(guī)則。第一條規(guī)則適用于輸入生命周期,而后兩條規(guī)則則適用于輸出生命周期。如果編譯器檢查完這三條規(guī)則并仍然存在沒有計(jì)算出生命周期的引用,編譯器將會(huì)停止并生成錯(cuò)誤。
- 對(duì)于輸入生命周期,每一個(gè)引用的參數(shù)都有它自己的生命周期參數(shù)。每一個(gè)被省略的函數(shù)參數(shù)成為一個(gè)不同的生命周期參數(shù)。
- 如果只有一個(gè)輸入生命周期參數(shù),它被賦給所有輸出聲明周期參數(shù)。
- 如果方法有多個(gè)輸入生命周期參數(shù),其中之一因?yàn)榉椒ǖ木壒适?
&self或&mut self,那么self的生命周期被賦給所有輸出生命周期參數(shù)。這使得方法寫起來更簡潔。
方法定義中的生命周期注解
實(shí)現(xiàn)方法時(shí),結(jié)構(gòu)體字段的生命周期必須總是在 impl 關(guān)鍵字之后聲明并在結(jié)構(gòu)體名稱之后被使用,因?yàn)檫@些生命周期是結(jié)構(gòu)體類型的一部分。
impl 塊里的方法簽名中,引用可能與結(jié)構(gòu)體字段中的引用相關(guān)聯(lián),也可能是獨(dú)立的。另外,生命周期省略規(guī)則也經(jīng)常讓我們無需在方法簽名中使用生命周期注解。
struct ImportantExcerpt<'a> {
part: &'a str,
}
impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 {
3
}
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("Attention please: {}", announcement);
self.part
}
}
靜態(tài)生命周期
'static 生命周期存活于整個(gè)程序期間。所有的字符串字面值都擁有 'static 生命周期。let s: &'static str = "I have a static lifetime.";