概念
-
變量(關(guān)鍵字:let)
- 變量默認(rèn)是不可改變的(immutable),如下的代碼運(yùn)行則會(huì)報(bào)錯(cuò)。
fn main() { let x = 5; println!("x的值為:{x}") x = 6; println!("x的值為:{x}") }- 盡管變量默認(rèn)是不可變的,你仍然可以在變量名前添加 mut 來使其可變,mut 也向讀者表明了其他代碼將會(huì)改變這個(gè)變量值的意圖。將上面的代碼修改如下,則可以正常運(yùn)行。
fn main() { let mut x = 5; println!("x的值為:{x}") x = 6; println!("x的值為:{x}") } -
常量(關(guān)鍵字:const)
- 類似于不可變變量,常量 (constants) 是綁定到一個(gè)名稱的不允許改變的值。
- 不允許對常量使用 mut。常量不光默認(rèn)不可變,它總是不可變。
- 常量可以在任何作用域中聲明,包括全局作用域。
- 常量只能被設(shè)置為常量表達(dá)式,而不可以是其他任何只能在運(yùn)行時(shí)計(jì)算出的值。
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3; -
隱藏
- 我們可以定義一個(gè)與之前變量同名的新變量,稱之為第一個(gè)變量被第二個(gè) 隱藏(Shadowing) 了。
- 當(dāng)我們使用變量的名稱時(shí),編譯器將看到第二個(gè)變量,任何使用該變量名的行為中都會(huì)視為是在使用第二個(gè)變量,直到第二個(gè)變量自己也被隱藏或第二個(gè)變量的作用域結(jié)束。
- 可以用相同變量名稱來隱藏一個(gè)變量,以及重復(fù)使用 let 關(guān)鍵字來多次隱藏,如下所示:
fn main() { let x = 5; let x = x + 1; { let x = x * 2; println!("The value of x in the inner scope is: {x}"); // 12 } println!("The value of x is: {x}"); // 6 } -
mut 與 隱藏(Shadowing)的區(qū)別
- 當(dāng)不小心嘗試對變量重新賦值時(shí),如果沒有使用 let 關(guān)鍵字,就會(huì)導(dǎo)致編譯時(shí)錯(cuò)誤。通過使用 let,我們可以用這個(gè)值進(jìn)行一些計(jì)算,不過計(jì)算完之后變量仍然是不可變的。
- 當(dāng)再次使用 let 時(shí),實(shí)際上創(chuàng)建了一個(gè)新變量,我們可以改變值的類型,并且復(fù)用這個(gè)名字。
- 而使用 mut 在修改值的時(shí)候,我們不能改變變量的類型。
數(shù)據(jù)類型
- 標(biāo)量:標(biāo)量(scalar)類型代表一個(gè)單獨(dú)的值。Rust 有四種基本的標(biāo)量類型:整型(有符號(i),無符號(u))、浮點(diǎn)型(f32,f64)、布爾類型(true,false)和字符類型(char)。
- 復(fù)合類型:復(fù)合類型(Compound types)可以將多個(gè)值組合成一個(gè)類型。Rust 有兩個(gè)原生的復(fù)合類型:元組(tuple)和數(shù)組(array)。
- 元組:將多個(gè)其他類型的值組合進(jìn)一個(gè)復(fù)合類型的主要方式,長度固定,一旦聲明,其長度不會(huì)增大或縮小??梢允褂媚J狡ヅ洌╬attern matching)來解構(gòu)(destructure)元組值。也可以使用點(diǎn)號(.)后跟值的索引來直接訪問它們。
fn main() { const tup: (i32, f64, u8) = (500, 6.4, 1); // 解構(gòu) let (x, y, z) = tup; println!("the value of y is: {y}"); // 6.4 // 直接訪問 let x1 = tup.0; let x2 = tup.1; let x3 = tup.2; println!("the values of x1 x2 x3 is: {x1} {x2} {x3}"); // 500 6.4 1 } - 數(shù)組:與元組不同,數(shù)組中的每個(gè)元素的類型必須相同,數(shù)組長度是固定的。vector 類型是標(biāo)準(zhǔn)庫提供的一個(gè)允許增長和縮小長度的類似數(shù)組的集合類型。當(dāng)不確定是應(yīng)該使用數(shù)組還是 vector 的時(shí)候,那么很可能應(yīng)該使用 vector。數(shù)組是在棧上分配空間。
// 定義已知長度的數(shù)組 let months = ["January", "February", "March", "April", "May", "June", "July","August", "September", "October", "November", "December"]; // 定義類型相同的,已知長度的數(shù)組 let a: [i32; 5] = [1, 2, 3, 4, 5]; // 定義長度為 5 都是數(shù)字 3 的數(shù)組 let a = [3; 5]; // 在棧 (stack) 上分配的已知固定大小的單個(gè)內(nèi)存塊??梢允褂盟饕齺碓L問數(shù)組的元素 let a = [1, 2, 3, 4, 5]; let first = a[0]; let second = a[1];
- 元組:將多個(gè)其他類型的值組合進(jìn)一個(gè)復(fù)合類型的主要方式,長度固定,一旦聲明,其長度不會(huì)增大或縮小??梢允褂媚J狡ヅ洌╬attern matching)來解構(gòu)(destructure)元組值。也可以使用點(diǎn)號(.)后跟值的索引來直接訪問它們。
函數(shù)
- 在函數(shù)簽名中,必須 聲明每個(gè)參數(shù)的類型。
- 函數(shù)體由一系列的語句和一個(gè)可選的結(jié)尾表達(dá)式構(gòu)成。
- 語句和表達(dá)式:
- 語句:是執(zhí)行一些操作但不返回值的指令。
// 整個(gè)函數(shù)定義也是一個(gè)語句 fn main() { let y = 6; // 是一個(gè)語句 }- 表達(dá)式:計(jì)算并產(chǎn)生一個(gè)值。表達(dá)式的結(jié)尾沒有分號。如果在表達(dá)式的結(jié)尾加上分號,它就變成了語句,而語句不會(huì)返回值。
fn main() { // 這是一個(gè)表達(dá)式 let y = { let x = 3; x + 1 }; println!("The value of y is: {y}"); // 值是 4 let x = five(); println!("The value of x is: {x}"); // 5 et x = plus_one(5); println!("The value of x is: {x}"); } // 在 five 函數(shù)中沒有函數(shù)調(diào)用、宏、甚至沒有 let 語句 —— 只有數(shù)字 5。這在 Rust 中是一個(gè)完全有效的函數(shù)。 fn five() -> i32 { 5 } // 錯(cuò)誤:期望返回一個(gè)i32類型的值,這里加上了分號,變成了語句,沒有返回值 fn plus_one(x: i32) -> i32 { x + 1; // 去掉分號才正確 }
控制流
- if 表達(dá)式
- 根據(jù)條件執(zhí)行不同的代碼分支。不會(huì)嘗試自動(dòng)地將非布爾值轉(zhuǎn)換為布爾值。
- 在 let 語句中使用 if,if 是一個(gè)表達(dá)式,我們可以在 let 語句的右側(cè)使用它。
fn main() { let number = 3; if number < 5 { println!("condition was true"); } else { println!("condition was false"); } // 報(bào)錯(cuò),不會(huì)自動(dòng)類型轉(zhuǎn)換,if 后面必須是布爾值 if number { println!("number was three"); } // 在 let 語句中使用 if let condition = true; let number = if condition { 5 } else { 6 }; println!("The value of number is: {number}"); // 5 let number = if condition { 5 } else { "six" }; // 報(bào)錯(cuò),if 代碼塊中的表達(dá)式返回一個(gè)整數(shù),而 else 代碼塊中的表達(dá)式返回一個(gè)字符串。 } - 循環(huán)
- Rust 有三種循環(huán):loop、while 和 for。
- loop
- 關(guān)鍵字告訴 Rust 一遍又一遍地執(zhí)行一段代碼直到你明確要求停止。
fn main() { loop { println!("again"); // 重復(fù)打印 again,ctrl + c 終止程序,才能停止 } }- 使用 break 關(guān)鍵字終止程序,并返回值
fn main() { let mut counter = 0; let result = loop { counter += 1; if(counter == 10) { break counter * 2; } } println!("The result is {result}"); // 20 }- 使用循環(huán)標(biāo)簽,可以在多個(gè)循環(huán)之間消除歧義
fn main() { let mut count = 0; 'counting_up': loop { println!("count = {count}"); let mut remaining = 10; loop { println!("remaining = {remaining}"); if remaining == 9 { break; } if count == 2 { break 'counting_up'; // count == 2 時(shí),終止外層循環(huán) } remaining -= 1; } count += 1; } println!("End count = {count}"); } /** * count = 0 remaining = 10 remaining = 9 count = 1 remaining = 10 remaining = 9 count = 2 remaining = 10 End count = 2 */ - while 條件循環(huán)
- 當(dāng)條件為 true,執(zhí)行循環(huán)。當(dāng)條件不再為 true,調(diào)用 break 停止循環(huán)??梢酝ㄟ^ loop、if、else 和 break 來實(shí)現(xiàn)相同效果。
- 這種結(jié)構(gòu)消除了很多使用 loop、if、else 和 break 時(shí)所必須的嵌套,這樣更加清晰。當(dāng)條件為 true 就執(zhí)行,否則退出循環(huán)。
fn main() { // 倒計(jì)時(shí)的while實(shí)現(xiàn) let mut number = 3; while number != 0 { println!("number = {number}"); number -= 1; } // 如果使用 for, rev 用來反轉(zhuǎn) range for number in (1..4).rev() { println!("number = {number}"); } println!("LIFTOFF!!!"); } - for 循環(huán)
- 在循環(huán)遍歷數(shù)組時(shí),使用while循環(huán),可能會(huì)出現(xiàn)越界的情況,更為推薦的是使用for循環(huán),增加代碼的安全性。
fn main() { let a = [1, 2, 3, 4, 5]; let mut index = 0; while index < 5 { // 如果數(shù)組長度變 4, 而忘記了更改這里的 5 就會(huì)使程序 panic println!("the value is: {}", a[index]); index += 1; } // for 循環(huán) 作為更簡潔的替換方案 for element in a { println!("the value is: {element}"); } }
- loop
- Rust 有三種循環(huán):loop、while 和 for。
-
所有權(quán)
所有權(quán)(系統(tǒng))是 Rust 最為與眾不同的特性,對語言的其他部分有著深刻含義。它讓 Rust 無需垃圾回收(garbage collector)即可保障內(nèi)存安全,因此理解 Rust 中所有權(quán)如何工作是十分重要的。
-
規(guī)則
- 每一個(gè)值都有一個(gè) 所有者(owner)。
- 值在任一時(shí)刻有且只有一個(gè)所有者。
- 當(dāng)所有者(變量)離開作用域,這個(gè)值將被丟棄。
下面的例子跟普通的大括號作用域類似,它是硬編碼進(jìn)程序里的字符串值,是不可變的。
- 當(dāng)所有者(變量)離開作用域,這個(gè)值將被丟棄。
{ // s 在這里無效,它尚未聲明 let s = "hello world"; // 從此處起,s 是有效的 // 使用 s } // 此作用域結(jié)束,s 不再有效如果要處理動(dòng)態(tài)長度,則需要將使用到另一種字符串類型:String。
- 必須在運(yùn)行時(shí)向內(nèi)存分配器(memory allocator)請求內(nèi)存。
- 需要一個(gè)當(dāng)我們處理完 String 時(shí)將內(nèi)存返回給分配器的方法。
let mut s = String::from("hello"); s.push_str(", world"); // 在字符串后面追加字面值 println!("s = {s}"); // hello, world當(dāng)離開作用域時(shí),rust 會(huì)自動(dòng)調(diào)用drop,進(jìn)行垃圾回收。
- 拷貝和移動(dòng)
// 將 5 綁定到 x;接著生成一個(gè)值 x 的拷貝并綁定到 y,這里 5 是存儲(chǔ)在棧中,所以是拷貝 let x = 5; let y = x; // 這里s2拷貝了s1存儲(chǔ)在棧中指針,對應(yīng)著堆中的同一份數(shù)據(jù),同時(shí),s1 將失效,防止s2 和 s1離開作用域時(shí),釋放相同的內(nèi)存,導(dǎo)致二次釋放的錯(cuò)誤 // 不是淺拷貝,這個(gè)操作叫做 移動(dòng) let s1 = String::from("hello"); let s2 = s1;- 克隆
// 這里深度復(fù)制了堆上的數(shù)據(jù),類似于深拷貝 let s1 = String::from("hello"); let s2 = s1.clone(); println!("s1 = {}, s2 = {}", s1, s2); -
所有權(quán)與函數(shù)
fn main() { let s = String::from("hello"); // s 進(jìn)入作用域 take_ownership(s); // s 的值移動(dòng)到函數(shù)里 ... // ... 這里不再有效 let x = 5; // x 進(jìn)入作用域 makes_copy(x); // x 的值移動(dòng)到函數(shù)里 // 但 i32 是 copy 的 // 所以在后面可以繼續(xù)使用 x } // 這里:x 先移除作用域,然后是 s,因?yàn)?s 的值已經(jīng)被移走 fn takes_ownership(some_string: String) { // some_string 進(jìn)入作用域 println!("{}", some_string); } // 這里,some_string 移出作用域并調(diào)用 `drop` 方法。 // 占用的內(nèi)存被釋放 fn makes_copy(some_integer: i32) { // some_integer 進(jìn)入作用域 println!("{}", some_integer); } // 這里,some_integer 移出作用域。沒有特殊之處作為一個(gè)通用的規(guī)則,任何一組簡單標(biāo)量值的組合都可以實(shí)現(xiàn) Copy,任何不需要分配內(nèi)存或某種形式資源的類型都可以實(shí)現(xiàn) Copy 。如下是一些 Copy 的類型:
- 所有整數(shù)類型,比如 u32。
- 布爾類型,bool,它的值是 true 和 false。
- 所有浮點(diǎn)數(shù)類型,比如 f64。
- 字符類型,char。
- 元組,當(dāng)且僅當(dāng)其包含的類型也都實(shí)現(xiàn) Copy 的時(shí)候。比如,(i32, i32) 實(shí)現(xiàn)了 Copy,但 (i32, String) 就沒有。
-
返回值與作用域
返回值也可以轉(zhuǎn)移所有權(quán)。fn main() { let s1 = gives_ownership(); // gives_ownership 將返回值 // 轉(zhuǎn)移給 s1 let s2 = String::from("hello"); // s2 進(jìn)入作用域 let s3 = takes_and_gives_back(s2); // s2 被移動(dòng)到 // takes_and_gives_back 中, // 它也將返回值移給 s3 } // 這里,s3 移出作用域并被丟棄。s2 也移出作用域,但已被移走, // 所以什么也不會(huì)發(fā)生。s1 離開作用域并被丟棄 fn gives_ownership() -> String { // gives_ownership 會(huì)將 // 返回值移動(dòng)給 // 調(diào)用它的函數(shù) let some_string = String::from("yours"); // some_string 進(jìn)入作用域。 some_string // 返回 some_string // 并移出給調(diào)用的函數(shù) // } // takes_and_gives_back 將傳入字符串并返回該值 fn takes_and_gives_back(a_string: String) -> String { // a_string 進(jìn)入作用域 // a_string // 返回 a_string 并移出給調(diào)用的函數(shù) }變量的所有權(quán)總是遵循相同的模式:
- 將值賦給另一個(gè)變量時(shí)移動(dòng)它。
- 當(dāng)持有堆中數(shù)據(jù)值的變量離開作用域時(shí),其值將通過 drop 被清理掉,除非數(shù)據(jù)被移動(dòng)為另一個(gè)變量所有。
-
引用與借用
// 不使用引用,取值的所有權(quán) fn main() { let s1 = String::from("hello"); let (s2, len) = calc_length(s1); println!("The length of '{}' is {}.", s2, len); } fn calc_length(s: String) -> (String, usize) { let length = s.len(); (s, length) // 通過返回值來交還所有權(quán) } // 使用引用,它以一個(gè)對象的引用作為參數(shù)而不是獲取值的所有權(quán): fn main() { let s1 = String::from("hello"); let len = calc_length(&s1); println!("The length of '{}' is {}.", s1, len); } fn calc_length(s: &String) -> usize { // s 是 String 的引用 s.len() } // 這里,s 離開了作用域。但因?yàn)樗⒉粨碛幸弥档乃袡?quán), // 所以什么也不會(huì)發(fā)生(默認(rèn))不允許修改引用的值,如果要修改,要使用可變引用
fn main() { let mut s = String:from("hello"); change($mut s); } change(some_string: &mut String) { some_string.push_str(", world"); }不允許同時(shí)擁有多個(gè)對該變量的可變引用,但可以是不可變引用
let mut s = String::from("hello"); let r1 = &mut s; let r2 = &s; // 沒問題 let r3 = &s; // 沒問題 let r4 = &mut s; // 不可行 { let r5 = &mut s; } // r5 在這里離開了作用域,所以我們完全可以創(chuàng)建一個(gè)新的引用 let r6 = &mut s; // 可行- 垂直引用
// 錯(cuò)誤 fn main() { let reference_to_nothing = dangle(); } fn dangle() -> &String { let s = String::from("hello"); &s } // 這里 s 離開作用域并被丟棄。其內(nèi)存被釋放。所以上面返回的它的引用也是指向無效地址 // 直接返回 String 將控制權(quán)交出去,才是正確的做法,所有權(quán)被移動(dòng)出去,所以沒有值被釋放。 fn no_dangle() -> String { let s = String::from("hello"); s }引用的規(guī)則總結(jié)
- 在任意給定時(shí)間,要么只有一個(gè)可變引用,要么只能有多個(gè)不可變引用。
- 引用必須總是有效的。
-
slice 類型
slice 允許你引用集合中一段連續(xù)的元素序列,而不用引用整個(gè)集合。slice 是一種引用,所以它沒有所有權(quán)。fn main() { let s = String::from("hello world"); let index = first_word(&s); // 需要關(guān)注 index 值的有效性,如果 s 被清空,仍然用index去訪問,就會(huì)出現(xiàn)問題 let hello = &s[0..5]; // 前閉后開 等同于 &s[..5] let world = &s[6..11]; // 等同于 &s[6..] &s[6..s.len()] } fn first_word(s: &String) -> usize { let bytes = s.as_bytes(); for(i, &item) in bytes.iter().enumerate() { if item == b' ' { return i } } s.len() } // 用切片實(shí)現(xiàn)上面的功能 fn use_slice_first_word(s: &String) -> &str { let bytes = s.as_bytes(); for(i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[0..i]; } } &s[..] // 整個(gè) string }
-
結(jié)構(gòu)體
- struct,或者 structure,是一個(gè)自定義數(shù)據(jù)類型,允許你包裝和命名多個(gè)相關(guān)的值,從而形成一個(gè)有意義的組合。
- 定義
struct User { active: bool, username: String, email: String, sign_in_count: u64, } - 使用
可以使用點(diǎn)號來獲取某個(gè)特定的值。如果結(jié)構(gòu)體實(shí)例是可變的,還可以進(jìn)行賦值。fn main() { let user1 = User { active: true, username: String::from("someusername123"), email: String::from("someone@example.com"), sign_in_count: 1, }; }
注意:整個(gè)實(shí)例必須是可變的,不允許只將某個(gè)字段標(biāo)記為可變。
們可以在函數(shù)體的最后一個(gè)表達(dá)式中構(gòu)造一個(gè)結(jié)構(gòu)體的新實(shí)例,來隱式地返回這個(gè)實(shí)例。fn main() { let mut user2 = User { active: true, username: String::from("someusername123"), email: String::from("someone@example.com"), sign_in_count: 1, } } user2.email = String::from("anotheremail@example.com"); // 使使用結(jié)構(gòu)體更新語法從其他實(shí)例創(chuàng)建實(shí)例 let user3 = User { active: user2.active, username: user2.username, email: String::from("other@example.com"), sign_in_count: user2.sign_in_count, } // 使用 user3 中的一個(gè)值創(chuàng)建一個(gè)新的 User 實(shí)例 let user4 = User { email: String::from("another@example.com"); ..user3, // 必須放在最后,以指定其余的字段應(yīng)從 user3 的相應(yīng)字段中獲取其值,但我們可以選擇以任何順序?yàn)槿我庾侄沃付ㄖ担挥每紤]結(jié)構(gòu)體定義中字段的順序。 }
定義元組結(jié)構(gòu)體fn build_user(email: String, username: String) -> User { User { active: true, username: username, // 可以簡寫為:username, email: email, // 可以簡寫為:email, sign_in_count: 1, } }
元組結(jié)構(gòu)體有著結(jié)構(gòu)體名稱提供的含義,但沒有具體的字段名,只有字段的類型。
unit 單元結(jié)構(gòu)體struct Color(i32, i32, i32); struct Point(i32, i32, i32); fn main() { let black = Color(0, 0, 0); let origin = Point(0, 0, 0); } // 注意 black 和 origin 值的類型不同,因?yàn)樗鼈兪遣煌脑M結(jié)構(gòu)體的實(shí)例struct Unit;