The Rust Programming Language
Rust 編程語(yǔ)言筆記。
來(lái)源:The Rust Programming Language By Steve Klabnik, Carol Nichols 。
翻譯參考:Rust 語(yǔ)言術(shù)語(yǔ)中英文對(duì)照表
Rustaceans
Rustaceans 指的是使用過(guò)、貢獻(xiàn)過(guò)或?qū)?Rust 語(yǔ)言有興趣的用戶。
安裝
使用 rustup 來(lái)安裝 Rust。
Rust 源文件以 .rs 作為擴(kuò)展名。
幾個(gè)常用的命令:
- 編譯:
rustc .rs-file - 運(yùn)行:
./compiled-file - 檢查 Rust 編譯器版本:
rustc --version - 檢查
rustup版本:rustup --version - 更新 Rust:
rustup update - 卸載 Rust 和
rustup:rustup self uninstall - 本地文檔:
rustup doc
Rust 是預(yù)(ahead-of-time)編譯語(yǔ)言,意思是可以把編譯后生成的可執(zhí)行文件發(fā)送給別人,他們可以在不安裝 Rust 的前提下運(yùn)行該文件。
Cargo
Cargo 是 Rust 的構(gòu)建系統(tǒng)和包管理工具。
幾個(gè)常用的命令:
- 檢查 Cargo 的版本:
cargo --version - 新建項(xiàng)目:
cargo new - 項(xiàng)目構(gòu)建:
cargo build - 運(yùn)行項(xiàng)目:
cargo run - 項(xiàng)目檢查:
cargo check:該命令確保項(xiàng)目可以編譯,但不生成可執(zhí)行文件,速度比c--argo build更快 - 發(fā)布:
cargo build --release
在運(yùn)行 cargo build 后,Rust 會(huì)把編譯后的二進(jìn)制文件放在 target/debug 文件夾下。
包
在 Rust 中,包被稱為 crates(也可以不翻譯)。
Rust 使用 TOML(Tom's Obvious Minimal Language) 格式來(lái)管理依賴。
使用 cargo new 創(chuàng)建新項(xiàng)目后,在該項(xiàng)目的目錄下會(huì)有名為 Cargo.toml 的文件來(lái)管理依賴和包。
可以使用 cargo add 來(lái)添加依賴,也可以在 .toml 文件的 [dependencies] 字段下添加包名。
在第一次使用 cargo build 后,Rust 會(huì)在根目錄生成名為 Cargo.lock 的文件用于追蹤包/依賴的版本。
例如:
[dependencies]
rand = "0.8.5"
包的版本使用三位的標(biāo)準(zhǔn)包標(biāo)準(zhǔn)。
在 .rs 文件中使用關(guān)鍵字 use 來(lái)導(dǎo)入包。
規(guī)范
- Rust 的縮進(jìn)采用的是 4 個(gè)
space,而不是tab - Rust 使用 snake case 對(duì)變量和函數(shù)命名,也就是全部使用小寫字母,單詞中間使用下劃線
_連接,例如:var_name - Rust 使用 全大寫 對(duì)常量命名,單詞之間使用下劃線
_連接,例如:MINUTES_WITHIN_A_DAY
Hello World
fn main() {
println!("Hello, World!");
}
-
fn是函數(shù)(function)的關(guān)鍵詞 -
main()是 Rust 的主函數(shù)、類似于 C、C++ - 每行結(jié)束需要用分號(hào)
;表示
基礎(chǔ)編程概念
注釋
Rust 有三種注釋:
- 單行注釋:
// - 多行注釋:
/* */ - 文檔(DocString)注釋:
///或//!
變量和可變
變量
Rust 是靜態(tài)類型語(yǔ)言,在聲明變量時(shí)需要使用關(guān)鍵詞 let 并在冒號(hào) : 后指明變量的類型。這點(diǎn)類似于 Python 的 Type-Hint 以及 TypeScript。
let var: u8 = 0;
可變
Rust 中的所有變量都有可變(mutable)或不可變(immutable)。
如果在聲明變量時(shí)不明確指明,那么默認(rèn)為不可變。
使用關(guān)鍵字 mut 使得變量可變。
let mut var: u8 = 2;
u8 = 3; // That's OK
let ano_var: u8 = 2;
u8 = 3; // Error
隱藏、覆蓋 Shadow
fn main() {
let x: u32 = 5;
let x = x + 1;
{
let x = x * 2;
println!("The inner x: {x}");
}
println!("The outer x: {x}");
}
// The inner x: 12
// The outer x: 6
如上述代碼所示:聲明了多個(gè)名為 x 的變量,在 Rust 中,我們稱 第一個(gè) x 被第二個(gè) x shadow(隱藏、覆蓋),或者 第二個(gè) x overshadow 了第一個(gè) x,這意味著:當(dāng)使用 x 這個(gè)變量時(shí),編譯器總是使用第二個(gè) x,即第二個(gè) x 拿走了第一個(gè) x 的所有,直到程序流離開第二個(gè) x 的作用域或者第二個(gè) x 也被 shadow。
shadow 和 mut 的區(qū)別
-
shadow 使得可以改變同名變量的類型,但是
mut會(huì)在編譯時(shí)出錯(cuò)fn main() { let x: &str = " "; let x: usize = x.len(); // 5 let mut x: &str = " "; x = x.len() // Error } -
shadow 使得可以變不可變的同名變量的值
fn main() { let x: u32 = 5; x = 6; // Error, 因?yàn)闆](méi)有變量默認(rèn)是不可變的 let x: u32 = 5; let x = 6; println!("{}", x) // 6 }
常量
Rust 使用 const 關(guān)鍵字聲明常量。通常來(lái)說(shuō),需要用大寫字母聲明常量。
const THE_DAY_I_START_LEARNING_RUST: String = "2023.5.9";
不可變的變量和常量的區(qū)別在于:
- 常量只能是不可變的,不能通過(guò)
mut關(guān)鍵字使得常量可變 - 常量通常只賦值給常量聲明的表達(dá)式,而不是運(yùn)行時(shí)的計(jì)算表達(dá)式
- 常量可以在任何作用域(scope) 中聲明,在程序運(yùn)行期間,在其聲明的作用域中是有效的
數(shù)據(jù)類型
Rust 是靜態(tài)類型語(yǔ)言,因此在編譯時(shí)就需要知道所有的變量類型。
例如在數(shù)據(jù)轉(zhuǎn)換中:
fn main() {
let var = "100".parse().expect("Not a number!"); // Error
let var: u32 = "100".parse().expect("Not a number"); // OK, var == 100
}
基本類型
Rust 的基本數(shù)據(jù)類型有 4 種:
- 布爾型 bool
- 整型 integer
- 浮點(diǎn)型 float
- 字符型 char
布爾型
布爾型有兩個(gè)值:
- true
- false
整型
整型有 6 種長(zhǎng)度,每種長(zhǎng)度分為有符號(hào)和無(wú)符號(hào)兩類。
| 長(zhǎng)度 | 有符號(hào) | 無(wú)符號(hào) |
|---|---|---|
| 8-bit | i8 | u8 |
| 16-bit | i16 | u16 |
| 32-bit | i32 | u32 |
| 64-bit | i64 | u64 |
| 128-bit | i128 | u128 |
| arch | isize | usize |
整型字面量(literal)
| 字面量 | 舉例 |
|---|---|
| 十進(jìn)制 | 98_222 |
| 十六進(jìn)制 | 0xff |
| 八進(jìn)制 | 0o77 |
| 二進(jìn)制 | 0b1111_0000 |
| 字節(jié)(u8) | b'A' |
- 整型的默認(rèn)類型為
i32 - 在使用集合類數(shù)據(jù)類型的索引時(shí),使用
usize或isize
浮點(diǎn)型
浮點(diǎn)型有兩類,而且都是有符號(hào)的:
f32-
f64:默認(rèn)類型
字符型
字符型用 c (character)標(biāo)識(shí)。
復(fù)合類型
Rust 的復(fù)合類型包括:
- 元組
- 數(shù)組
元組
元組 tuple:用括號(hào) () 聲明,元組元素的數(shù)據(jù)類型可以不同。
元組是定長(zhǎng)的,一旦聲明后就不能改變大小。
let tup: (u32, f32, bool) = (1, 2.2, true);
和 Python 不同,Rust 中的元組可以使用 . 運(yùn)算符來(lái)訪問(wèn)。
let tup: (u32, f32, bool) = (1, 2.2, true);
println!("{}", tup.0); // 1
println!("{}", tup.2); // 2.2
println!("{}", tup.3); // true
沒(méi)有元素的元組被稱為單元(Unit),寫作 (),表示空值和空返回類型。
數(shù)組
數(shù)組 array: 用方括號(hào) [] 聲明,數(shù)組元素的數(shù)據(jù)類型必須相同。
數(shù)組也是定長(zhǎng)的。
let arr: [u8; 5] = [1, 2, 3, 4, 5];
let arr_2: [3; 5]; // [5, 5, 5]
; 的前后分別表示數(shù)組元素的類型和數(shù)組元素的個(gè)數(shù)(如果前面用數(shù)字表示,則重復(fù)后面的數(shù)字前面次)。
可以使用索引 [],訪問(wèn)數(shù)組元素,因?yàn)閿?shù)組以定長(zhǎng)形式存儲(chǔ)在棧中:
let first = arr[0];
如果使用越界索引訪問(wèn)數(shù)組:
let arr: [u8; 5] = [1, 2, 3, 4, 5];
println!("{}", arr[5]); // runtime error
會(huì)導(dǎo)致 運(yùn)行時(shí) 錯(cuò)誤。Rust 會(huì)在運(yùn)行時(shí)檢查是否有越界行為。
控制流
條件
用 if, else if, else 等關(guān)鍵字控制程序流。
Rust 的條件語(yǔ)句不需要外嵌括號(hào) (),但是也不同于 Python 的 :,條件執(zhí)行體需要用花括號(hào):{}。
fn main() {
let var: u32 = 8;
if var > 0 {
println!("{var} is greater than 0!");
} else if var < 0 {
println!("{var} is less than 0!");
} else {
println!("{var} is equal to 0!");
}
}
注:與 JavaScript、Ruby 等語(yǔ)言不同,Rust 不會(huì)在條件判斷時(shí)自動(dòng)轉(zhuǎn)換變量的類型,例如:
fn main() {
let var: bool = true;
if var == 0 {
println!("You can't get there in Rust");
}
}
更簡(jiǎn)潔的寫法是在 let 中使用 if:
fn main() {
let condition: bool = true;
let number: u32 = if condition { 5 } else { 6 };
println!("{}", number) // 5
}
循環(huán)
Rust 中有 3 種循環(huán):
- 無(wú)限循環(huán)
loop -
while循環(huán) -
for循環(huán)
結(jié)束、跳出循環(huán)的關(guān)鍵字:
-
break跳出當(dāng)前循環(huán) -
continue結(jié)束本次循環(huán)(開始下一次循環(huán))
loop 循環(huán)
Rust 可以使用關(guān)鍵字 loop 來(lái)創(chuàng)建無(wú)限循環(huán),例如:
loop {
println!("This is a infinite loop");
}
// 等價(jià)于
while true {
println!("This is a infinite loop");
}
continue 對(duì) loop 循環(huán)無(wú)效。
可以在 break 后添加跳出循環(huán)的返回值。
fn main() {
let mut count = 0;
let reture_value = loop {
println!("{}", count);
count += 1;
if count == 5 {
break count * 2;
}
}
}
可以用 ``name給loop` 循環(huán)命名(標(biāo)識(shí)符)以跳出多層循環(huán),例如:
fn main() {
let mut count: u32 = 10;
`outer: loop {
let mut remaining: u32 = 2;
loop {
println!("{}", count * remaining);
remaining -= 1;
if remaining == 0 {
break outer;
}
}
count -= 1;
println!("{}", count);
if count == 0 {
break;
}
}
}
while 循環(huán)
類似于其他語(yǔ)言的 while 循環(huán)。
同樣,Rust 的循環(huán)條件語(yǔ)句不需要外嵌圓括號(hào) (),但是也不同于 Python 的 :,循環(huán)體需要用花括號(hào):{}。
fn main() {
let mut count = 10;
while count > 0 {
println!("{}", count);
count -= 1;
}
}
for 循環(huán)
for in
for in 循環(huán)對(duì)遍歷對(duì)象中的每一個(gè)元素都執(zhí)行遍歷:
fn main() {
let arr: [u32; 5] = [1, 2, 3, 4, 5];
for element in arr {
println!("{}", element);
}
}
類似 Python 的同名函數(shù)。
fn main() {
for i in 0..5 {
println!("{}", i);
}
}
使用 .rev() 倒序輸出:
fn main() {
for i in (0..5).rev() {
println!("{}", i);
}
}
函數(shù)
使用關(guān)鍵字 fn 聲明函數(shù),Rust 總是首先執(zhí)行 main 函數(shù)。
fn main() {
let var: u32 = 3;
increment_and_print(var);
}
fn increment_print(num: u32) -> u32 {
num += 1;
println!("{}", num);
return num;
// num
}
如上述代碼所示:Rust 并不關(guān)心函數(shù)定義的位置,因此在 main 函數(shù)前后定義其他函數(shù)都不會(huì)錯(cuò)誤。
函數(shù)的參數(shù)需要聲明名稱和類型,返回值用 -> 表示,只需要聲明類型。
可以使用關(guān)鍵字 return 顯式返回值,也可以把返回值寫在最后一行(不要使用 ; 結(jié)尾)。Rust 默認(rèn)把最后一行的值作為返回值。
語(yǔ)句 VS 表達(dá)式
Rust 是基于表達(dá)式的語(yǔ)言。在 Rust 中,語(yǔ)句(statements)和表達(dá)式(expressions)有明顯的區(qū)別:
- 語(yǔ)句:一段完成行為的指令,不返回值,以分號(hào)結(jié)尾。
- 表達(dá)式:給結(jié)果返回值,可以是語(yǔ)句的一部分,不以分號(hào)結(jié)尾(通過(guò)添加分號(hào)把表達(dá)式轉(zhuǎn)化為語(yǔ)句,同時(shí)失去返回值)
let y = (let x = 6); // Error
上面的代碼中,賦值語(yǔ)句并不返回任何值,因此,不能把 let x = 6 賦值給 y。這不同于 C、Ruby 等語(yǔ)言,其賦值語(yǔ)句會(huì)返回賦予的值。
而對(duì)于表達(dá)式:
{
let x: u32 = 6;
x + 1 // 返回 7
}
輸入和輸出
輸入
使用標(biāo)準(zhǔn)庫(kù) std::io 來(lái)導(dǎo)入標(biāo)準(zhǔn)庫(kù)的 io。
use std::io;
fn main() {
let mut inp: String = String::new();
io::stdin.read_line(&mut inp).expect("Failed to read");
println!("{inp}");
}
- 使用
stdin引入標(biāo)準(zhǔn)輸入流, - 使用
read_line(&)來(lái)讀取輸入, - 使用
expect()來(lái)處理異常情況。
輸出
使用 println!() 來(lái)輸出內(nèi)容。
println!() 表示調(diào)用 Rust 宏(macro),println() 表示調(diào)用函數(shù)
Rust 不支持直接輸出變量的值,必須格式化(format)后輸出。在引號(hào) "" 中使花括號(hào) {} 來(lái)輸出變量。
fn main() {
let var: u32 = 15;
println!("{}", var); // 15
// println!("var equals {var}") Also ok!
// println!(var) Error!
}
所有權(quán) Ownership
Ownership 是 Rust 語(yǔ)言管理內(nèi)存的規(guī)則。
Ownership 是 Rust 語(yǔ)言最重要的特性之一,也是為什么 Rust 比 C++ 更安全的原因。
Ownership
棧內(nèi)存和堆內(nèi)存
要理解什么是 Ownership,首先要了解棧內(nèi)存(stack)和堆內(nèi)存(heap)的區(qū)別。
簡(jiǎn)單來(lái)講,棧內(nèi)存和堆內(nèi)存都是程序在運(yùn)行時(shí)使用的內(nèi)存。
棧最重要的操作是入棧和出棧,入棧時(shí)把元素放在棧頂,出棧時(shí)把棧頂元素彈出,因此棧內(nèi)存中的元素是有先后順序的。棧內(nèi)存中存儲(chǔ)的是定長(zhǎng)的數(shù)據(jù),而在編譯時(shí)未知大小或者大小可能改動(dòng)的數(shù)據(jù)會(huì)存儲(chǔ)在堆內(nèi)存中。
堆內(nèi)存中的元素沒(méi)有先后順序,每次都要給元素分配足夠大的空間。當(dāng)數(shù)據(jù)需要存放在堆中時(shí),內(nèi)存分配器會(huì)分配給數(shù)據(jù)一塊空閑區(qū)域,并返回一個(gè)指針,指向這塊區(qū)域的地址。該指針是定長(zhǎng)的,因此可以存儲(chǔ)在棧中,但是如果要訪問(wèn)堆中的數(shù)據(jù),必須要沿著指針指向/存儲(chǔ)的地址。
因此,棧內(nèi)存存儲(chǔ)數(shù)據(jù)和訪問(wèn)數(shù)據(jù)時(shí)速度都要更快,而堆內(nèi)存則正好相反,因?yàn)槠浞謩e需要尋找空閑的內(nèi)存空間以及沿著指針尋找地址。
垃圾回收和動(dòng)態(tài)分配
對(duì)于靜態(tài)類型語(yǔ)言來(lái)說(shuō),所有變量都在內(nèi)存中占有一定的空間,該空間位置在 C、C++ 等語(yǔ)言中使用指針來(lái)表示。對(duì)于不使用的變量所占有的空間的釋放是編程語(yǔ)言設(shè)計(jì)時(shí)的關(guān)鍵問(wèn)題。通常應(yīng)該及時(shí)釋放其占有的空間,否則可能導(dǎo)致堆內(nèi)存溢出,引起難以預(yù)料的安全問(wèn)題;但如果釋放太早,則可能導(dǎo)致后續(xù)對(duì)該變量的引用出錯(cuò)。
對(duì)于該問(wèn)題,有一些語(yǔ)言(例如:Java)使用一種被稱為“垃圾回收”(Garbage Collection / GC)的機(jī)制來(lái)追蹤并釋放不使用變量的內(nèi)存。另一些語(yǔ)言(例如:C、C++)則需要用配對(duì)的 malloc(), free() 等函數(shù)來(lái)動(dòng)態(tài)分配和釋放空間。
Rust 則采用了 Ownership 機(jī)制。
Ownership 規(guī)則
- Rust 中的每個(gè)值都有一個(gè) owner
- 在同一時(shí)刻只能有一個(gè) owner
- 當(dāng) owner 超出作用域時(shí),該值會(huì)被丟棄
變量 ownership 的轉(zhuǎn)換
來(lái)看一個(gè)例子:如果兩個(gè)變量指向同一段內(nèi)存空間,此時(shí)如果釋放第一個(gè)變量指向的空間(也是第二個(gè)變量的指向),那么引用第二個(gè)變量是否會(huì)導(dǎo)致錯(cuò)誤?
fn main() {
let string_in_stack: &str = "This string is stored in stack";
let ano_string_in_stack: &str = string_in_stack;
println!("{}", string_in_stack);
println!("{}", ano_string_in_stack);
let string_in_heap: String = String::from("This string is stored in heap");
let ano_string_in_heap: String = string_in_heap;
println!("{}", string_in_heap); // value borrowed here after move
println!("{}", ano_string_in_heap);
}
上面的例子分別聲明了兩類字符串變量:
string_in_stack存儲(chǔ)在棧內(nèi)存中。也稱為“字符串字面量”,指的是通過(guò)雙引號(hào)來(lái)表示的字符串,這類字符串是不可變的(這里的不可變指的是不能更改字符串中某個(gè)字符,因?yàn)槠浯鎯?chǔ)是連續(xù)的),但可以變整個(gè)字符串。這類字符串更加高效,原因是在編譯時(shí)就已經(jīng)知道了其大小,而且是定長(zhǎng)的。-
string_in_heap存儲(chǔ)在堆內(nèi)存中。是由String類型創(chuàng)建的字符串,可以變。由于這類字符串是變長(zhǎng)的,因此在編譯時(shí)是不知道其內(nèi)容的,只能在運(yùn)行時(shí)請(qǐng)求在堆中分配內(nèi)存(通過(guò)String::from完成)。當(dāng)使用完該類字符串后,需要一種機(jī)制把內(nèi)存還給分配器:
- 帶有 GC 的語(yǔ)言會(huì)追蹤并清理不使用的內(nèi)存空間
- 沒(méi)有 GC 的語(yǔ)言需要程序員去識(shí)別并釋放內(nèi)存
- Rust 的做法是:一旦變量超出其作用域,該變量占據(jù)的內(nèi)存將被自動(dòng)回收(調(diào)用
drop函數(shù))
深、淺拷貝,移動(dòng)和克隆
回到代碼,存儲(chǔ)在堆上的字符串 string_in_heap 的內(nèi)存會(huì)被收回。這有些類似于淺拷貝(shallow copy),其只復(fù)制指針,長(zhǎng)度和 capacity,但是并不復(fù)制原數(shù)據(jù);但是 Rust 同時(shí)還釋放了被拷貝變量的空間,所以該操作被稱為移動(dòng)(move)。
因此,Rust 從來(lái)不會(huì)自動(dòng)執(zhí)行深拷貝(deep copy)。
如果確實(shí)要“深拷貝”堆中的數(shù)據(jù),可以使用 .clone()。
fn main() {
let string_in_heap: String = String::from("Hello");
let ano_string_in_heap: String = string_in_heap.clone();
println!("{} {}", string_in_heap, ano_string_in_heap);
}
// Hello Hello
因此,變量 ownership 的變化模式是相同的:
- 每當(dāng)賦值給另一變量時(shí),ownership move
- 當(dāng)堆中的變量超出作用域時(shí),ownership drop,除非 ownership 又被移交給其他的變量
Copy、Drop
Rust 中有一個(gè)被稱為 Copy 的 特質(zhì)(trait),可用于某種數(shù)據(jù)類型。實(shí)現(xiàn)了 Copy 的數(shù)據(jù)類型的變量可以賦值給另一個(gè)變量,而不會(huì)導(dǎo)致 move 操作。
Rust 不允許在實(shí)現(xiàn)了 Drop 特質(zhì)(trait) 的數(shù)據(jù)類型上實(shí)現(xiàn) Copy。
實(shí)現(xiàn) Copy 特質(zhì)(trait) 的數(shù)據(jù)類型包括:
- 所有的整型
- 所有的布爾型
- 所有的浮點(diǎn)型
- 所有的字符型
- 只包含上述四種的類型作為成員的元組
函數(shù)和返回值的 Ownership
函數(shù)和變量類似,給函數(shù)傳遞值會(huì)導(dǎo)致 move 或者 copy。
返回值也會(huì)轉(zhuǎn)移 ownership。
如果要讓函數(shù)使用變量但是不轉(zhuǎn)移 ownership,可以使用引用(reference)。
引用
引用和借用
引用(reference)類似于指針,它指向存儲(chǔ)某個(gè)數(shù)據(jù)的內(nèi)存。與指針不同的是,引用指向的數(shù)據(jù)總是有效的。
引用使得我們不需要帶走 ownership 就可以獲取數(shù)據(jù)。
使用引用的行為稱為借用(borrowing)。
使用引用
使用 & 來(lái)創(chuàng)建一個(gè)引用。
在使用引用類型時(shí),需要修改函數(shù)簽名,傳參方式等,例如:
fn main() {
let s1 = String::from("Hello");
let length = calculate_length(&s1); // 傳入引用 &
// The length of s1: 5
println!("The length of s1: {}", length);
}
fn calculate_length(s: &String) -> usize { // 函數(shù)參數(shù)類型為引用 &
s.len()
}
引用原則
在 Rust 中,引用的原則是:
- 只能同時(shí)有一個(gè)可變引用或者多個(gè)不可變引用
- 引用必須有效(valid)
可變和不可變引用
上述的引用是不可變的。類似于變量,如果想改變變量的值,需要使用可變引用。
在 & 后添加 mut 關(guān)鍵字來(lái)使用可變引用。
fn main() {
let mut s1 = String::from("Hello");
change(&mut s1); // 添加 mut
// s1: "Hello, World"
println!("s1: {}", s1);
}
fn change(s: &mut String) { // 添加 mut
s.push_str(", World")
}
可以同時(shí)使用多個(gè)不可變引用:
fn main() {
let mut s1 = String::from("Hello");
let r1 = &s1; // Ok
let r2 = &s1; // Ok
}
但是只能同時(shí)使用一個(gè)可變引用:
fn main() {
let mut s1 = String::from("Hello");
let r1 = &mut s1; // Ok (first borrow)
let r2 = &mut s1; // Err (cannot borrow)
}
也不能同時(shí)使用可變和不可變引用:
fn main() {
let mut s1 = String::from("Hello");
let r1 = &s1; // Ok (first borrow)
let r2 = &mut s1; // Err (cannot borrow)
}
除非作用域不同:
fn main() {
let mut s1 = String::from("Hello");
{
let r1 = &mut s1; // Ok (first borrow)
}
let r2 = &mut s1; // Ok
}
引用的作用域
引用的作用域從聲明開始到最后一次使用該引用。
例如下面的代碼沒(méi)有問(wèn)題:
fn main() {
let mut s1 = String::from("Hello");
let r1 = &s1; // Ok (first borrow)
let r2 = &s1; // Ok (second borrow)
println!("r1 and r2 {} {}", r1, r2); // r1 and r2 no longer used
let r3 = &mut s1; // Ok!
}
數(shù)據(jù)競(jìng)爭(zhēng)
Rust 的引用原則避免了數(shù)據(jù)競(jìng)爭(zhēng)可能導(dǎo)致的編譯時(shí)錯(cuò)誤。
數(shù)據(jù)競(jìng)爭(zhēng)指的是下述三種行為:
- 兩個(gè)或以上只能同時(shí)指向同一個(gè)數(shù)據(jù)
- 至少一個(gè)指針被用于寫入數(shù)據(jù)
- 沒(méi)有同步機(jī)制讀取數(shù)據(jù)
數(shù)據(jù)競(jìng)爭(zhēng)可能導(dǎo)致未定義的行為而且在運(yùn)行時(shí)難以診斷和調(diào)試。Rust 的引用限制能夠有效避免這類問(wèn)題。
有效引用和懸垂指針
懸垂指針(dangling point) 是指向了已經(jīng)被 free 內(nèi)存區(qū)域的指針。
在 Rust 中,編譯器保證引用必須有效。也就是引用必須發(fā)生在數(shù)據(jù)超出作用域前。
fn main() {
let mut s1 = return_string(); // error
}
fn return_string() -> &String {
let s = String::from("Hello");
&s
} // s out of scope
/* correct the above code
fn return_string() -> String {
let s = String::from("Hello");
s // return string not reference
}
*/
切片類型
切片(slice) 指的是對(duì)集合的部分引用。
語(yǔ)法
切片的語(yǔ)法為:[start_index..end_index],其中:
-
..是范圍(range)運(yùn)算符 -
start_index是開始的索引。(如果忽略該項(xiàng),默認(rèn)從0開始) -
end_index是最后的索引(不包括該索引位置)。(如果忽略該項(xiàng),默認(rèn)以集合的末尾截止)
字符串切片
下面是字符串切片:
fn main() {
let s1 = String::from("Hello, World");
let slice1 = &s1[0..5];
let slice2 = &s1[7..12];
println!("{} {}", slice1, slice2);
}
// Hello World
字符串切片的類型為 &str,因?yàn)槭遣豢勺円?& (非 & mut),因此,字符串字面量是不可變的。
解引用強(qiáng)制轉(zhuǎn)化
如果已知字符串切片,我們可以直接把它作為參數(shù)傳入;如果已知 String 類型,我們可以傳入該類型的切片或者對(duì) String 的引用。這種靈活性稱為解引用強(qiáng)制轉(zhuǎn)化(deref coercion)。
有經(jīng)驗(yàn)的 Rustaceans 會(huì)把 &String 類型為參數(shù)的函數(shù)簽名更改為 &str:
fn first_word(s: &String) -> &str {}
// to
fn first_word(s: &str) -> &str {}
這樣的改動(dòng)使得函數(shù)更加通用(general)同時(shí)并不損失任何功能。
例如:
fn main() {
let my_string = String::from("hello world");
// `first_word` works on slices of `String`s, whether partial or whole
let word = first_word(&my_string[0..6]);
let word = first_word(&my_string[..]);
// `first_word` also works on references to `String`s, which are equivalent
// to whole slices of `String`s
let word = first_word(&my_string);
let my_string_literal = "hello world";
// `first_word` works on slices of string literals, whether partial or whole
let word = first_word(&my_string_literal[0..6]);
let word = first_word(&my_string_literal[..]);
// Because string literals *are* string slices already,
// this works too, without the slice syntax!
let word = first_word(my_string_literal);
}
如果 first_word 函數(shù)的參數(shù)類型為 &String:
fn 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[..]
}
那么上述代碼就會(huì)報(bào)錯(cuò)!。
如果為 &str:
fn first_word(s: &str) -> &str { // 只修改函數(shù)參數(shù)的類型
// --snip--
}
代碼就能成功執(zhí)行。