柚子快報激活碼778899分享:Rust(2)進階語法
柚子快報激活碼778899分享:Rust(2)進階語法
Rust(2)進階語法
Author: Once Day Date: 2024年10月3日
一位熱衷于Linux學習和開發(fā)的菜鳥,試圖譜寫一場冒險之旅,也許終點只是一場白日夢…
漫漫長路,有人對你微笑過嘛…
全系列文章可參考專欄: 源碼分析_Once-Day的博客-CSDN博客
參考文章:
Rust 程序設(shè)計語言 - Rust 程序設(shè)計語言 簡體中文版 (kaisery.github.io)簡介 - Rust 參考手冊 中文版 (rustwiki.org)
文章目錄
Rust(2)進階語法1. 進階語法1.1 panic錯誤處理1.2 Result錯誤處理1.3 泛型數(shù)據(jù)類型1.4 Trait(共同特性)1.5 生命周期1.6 自動化測試1.7 迭代器1.8 文檔化注釋1.9 Box智能指針1.10 RC智能指針1.11 RefCell智能指針1.12 多線程編程1.13 模式匹配
1. 進階語法
1.1 panic錯誤處理
在 Rust 中,panic 是一種非常重要的錯誤處理機制。當程序遇到不可恢復的錯誤時,例如訪問數(shù)組越界、整數(shù)除零等,Rust 會主動調(diào)用 panic 來終止程序。這是 Rust 保證內(nèi)存安全的重要手段之一。
當 panic 發(fā)生時,Rust 會展開(unwind)調(diào)用棧并清理每個函數(shù)中的數(shù)據(jù)。這個過程有點類似其他語言中的異常拋出。如果 panic 沒有被捕獲處理,整個程序會終止并返回一個非零的錯誤碼。
在代碼中,可以使用 panic! 宏來主動觸發(fā)一個 panic,例如:
fn divide(x: i32, y: i32) -> i32 {
if y == 0 {
panic!("attempt to divide by zero");
}
x / y
}
這里如果傳入的除數(shù) y 為 0,divide 函數(shù)會調(diào)用 panic! 宏,阻止程序繼續(xù)運行,這可以避免后續(xù)的除零錯誤。
panic 主要用于處理不可恢復的錯誤狀況。對于可以恢復的錯誤,更推薦使用 Result 或 Option 來優(yōu)雅地傳遞和處理。
還可以使用 catch_unwind 函數(shù)來捕獲 panic,避免程序直接終止:
use std::panic;
let result = panic::catch_unwind(|| {
// 可能會 panic 的代碼
println!("hello!");
});
這里的閉包內(nèi)如果發(fā)生了 panic,會被 catch_unwind 捕獲,返回一個 Result,而不是讓程序崩潰。
1.2 Result錯誤處理
Result 是 Rust 標準庫中定義的一個枚舉類型,用于表示可能出錯的操作結(jié)果。它有兩個變體:
Ok(T) 代表操作成功,內(nèi)部包含一個類型為 T 的值;Err(E) 代表操作失敗,內(nèi)部包含一個類型為 E 的錯誤值。
其定義如下:
enum Result
Ok(T),
Err(E),
}
Result 在 Rust 中被廣泛用于錯誤處理和傳播。任何可能出錯的函數(shù),都可以返回一個 Result,讓調(diào)用者明確地處理可能出現(xiàn)的錯誤。例如,一個解析字符串為整數(shù)的函數(shù)可以這樣定義:
fn parse_int(s: &str) -> Result
// ...
}
這里 parse_int 函數(shù)返回一個 Result,成功時內(nèi)含解析得到的 i32 數(shù)字,失敗時內(nèi)含一個 ParseIntError 錯誤。調(diào)用者必須顯式地處理這個 Result。
Rust 提供了一系列方便的組合子函數(shù),用于處理和傳播 Result:
map: 對 Result 的 Ok 值進行映射轉(zhuǎn)換,保持 Err 值不變。and_then: 類似 map,但映射函數(shù)本身也返回一個 Result。map_err: 對 Result 的 Err 值進行映射轉(zhuǎn)換,保持 Ok 值不變。unwrap: 對 Ok 值進行解封,如遇 Err 則 panic。expect: 類似 unwrap,但可以指定 panic 時的錯誤信息。?運算符: 如果 Result 是 Err,則直接返回該 Err;如果是 Ok,則解封其中的值。
例如,我們可以使用 ? 運算符來傳播錯誤:
fn read_username_from_file() -> Result
let mut file = File::open("username.txt")?;
let mut username = String::new();
file.read_to_string(&mut username)?;
Ok(username)
}
這里 ? 會自動將 Ok 中的值解封,如遇 Err 則提前返回。這讓錯誤傳播的代碼非常簡潔。
1.3 泛型數(shù)據(jù)類型
泛型是 Rust 中一個非常強大和重要的特性,它允許我們編寫適用于多種類型的代碼,增強了代碼的重用性和表達力。Rust 中的泛型主要有以下幾種形式:
(1) 泛型函數(shù),可以在函數(shù)定義中使用泛型類型參數(shù),使函數(shù)能夠處理不同類型的參數(shù)。例如:
fn largest
let mut largest = list[0];
for &item in list {
if item > largest {
largest = item;
}
}
largest
}
這里的 largest 函數(shù)使用了泛型類型 T,可以找出任何實現(xiàn)了 PartialOrd 和 Copy trait 的類型的切片中的最大值。
(2) 泛型結(jié)構(gòu)體,可以在結(jié)構(gòu)體定義中使用泛型類型參數(shù),使結(jié)構(gòu)體能夠包含不同類型的字段。例如:
struct Point
x: T,
y: T,
}
這里的 Point 結(jié)構(gòu)體有一個泛型類型參數(shù) T,可以表示二維平面上任意類型的點。
(3) 泛型枚舉,可以在枚舉定義中使用泛型類型參數(shù),使枚舉能夠包含不同類型的值。最典型的例子就是標準庫中的 Option 和 Result:
enum Option
Some(T),
None,
}
enum Result
Ok(T),
Err(E),
}
(4) 泛型方法,可以在 impl 塊中定義泛型方法,使方法能夠處理不同類型的 self 參數(shù)或其他參數(shù)。例如:
impl
fn x(&self) -> &T {
&self.x
}
}
這里為 Point
(5) 泛型 trait,可以定義帶有泛型類型參數(shù)的 trait,描述一批類型的共同行為。例如:
trait Summary {
fn summarize(&self) -> String;
}
impl
fn summarize(&self) -> String {
format!("(Display) {}", self)
}
}
這里定義了一個 Summary trait,并為所有實現(xiàn)了 Display 的類型提供了一個默認的 summarize 實現(xiàn)。
泛型讓 Rust 具有了很強的表達力,可以編寫出抽象層次更高、適用范圍更廣的代碼。同時 Rust 編譯器會對泛型代碼進行單態(tài)化(monomorphization),生成具體類型的代碼,保證了運行時的高效性。
1.4 Trait(共同特性)
Trait 是 Rust 中一個非常重要和強大的特性,它用于定義和抽象類型的共同行為。Trait 類似于其他語言中的接口(Interface),但更加靈活和強大。
(1) 定義 Trait,可以使用 trait 關(guān)鍵字來定義一個 Trait。Trait 內(nèi)部可以包含方法簽名、關(guān)聯(lián)類型、常量等。
trait Summary {
fn summarize(&self) -> String;
}
這里定義了一個 Summary Trait,要求實現(xiàn)者必須提供一個 summarize 方法,該方法借用 &self,返回一個 String。
(2) 為類型實現(xiàn) Trait,可以使用 impl 關(guān)鍵字來為一個具體的類型實現(xiàn)某個 Trait。
struct NewsArticle {
headline: String,
location: String,
author: String,
content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
這里為 NewsArticle 結(jié)構(gòu)體實現(xiàn)了 Summary Trait,提供了具體的 summarize 方法。
(3) Trait Bound,可以使用 Trait Bound 來限制泛型類型參數(shù)必須實現(xiàn)某些 Trait。這樣可以在泛型函數(shù)內(nèi)部調(diào)用這些 Trait 的方法。
fn notify
println!("Breaking news! {}", item.summarize());
}
這里的泛型類型參數(shù) T 被限制為必須實現(xiàn) Summary Trait,因此可以在函數(shù)內(nèi)部調(diào)用 summarize 方法。
(4) Trait 作為函數(shù)參數(shù)和返回值,可以將 Trait 用作函數(shù)參數(shù)或返回值的類型,表示函數(shù)接受或返回任何實現(xiàn)了該 Trait 的類型。
fn returns_summarizable() -> impl Summary {
NewsArticle {
headline: String::from("Penguins win the Stanley Cup Championship!"),
location: String::from("Pittsburgh, PA, USA"),
author: String::from("Iceburgh"),
content: String::from("The Pittsburgh Penguins once again are the best hockey team in the NHL."),
}
}
這里的函數(shù)返回值類型是 impl Summary,表示返回任何實現(xiàn)了 Summary Trait 的類型。
(5) Trait 的默認實現(xiàn),在定義 Trait 時,可以為其中的某些方法提供默認實現(xiàn)。
trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}
這樣,如果某個類型實現(xiàn)了 Summary 但沒有提供自己的 summarize 實現(xiàn),就會使用這個默認實現(xiàn)。
(6) Trait 的繼承,一個 Trait 可以繼承另一個 Trait,這樣前者就包含了后者的所有方法。
trait Display {
fn display(&self) -> String;
}
trait Summary: Display {
fn summarize(&self) -> String {
format!("(Read more... {})", self.display())
}
}
這里 Summary Trait 繼承了 Display Trait,因此任何實現(xiàn) Summary 的類型也必須實現(xiàn) Display。
Trait 是 Rust 中實現(xiàn)抽象和多態(tài)的基礎(chǔ),也是 Rust 泛型編程的重要組成部分。通過 Trait,我們可以定義一套統(tǒng)一的接口,讓不同的類型共享相同的行為。同時 Trait Bound 讓我們能夠?qū)Ψ盒皖愋蛥?shù)進行靈活約束,保證了類型安全。
1.5 生命周期
生命周期是 Rust 所獨有的一個概念,它用于表示引用的有效范圍。在 Rust 中,每一個引用都有一個生命周期,它決定了這個引用在何時有效。生命周期的主要目的是避免懸垂引用(Dangling References),即引用在其引用的數(shù)據(jù)被釋放后仍然存在的情況。
(1) 生命周期的語法,生命周期在語法上用一個撇號 ’ 加上一個名字來表示,例如 'a, 'b, 'c 等。最常見的生命周期是 'static,它表示引用的數(shù)據(jù)在整個程序的運行期間都有效。
(2) 函數(shù)中的生命周期,當一個函數(shù)有引用類型的參數(shù)或返回值時,我們需要為這些引用指定生命周期。
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
這里的 longest 函數(shù)有兩個引用參數(shù) x 和 y,返回值也是一個引用。我們使用泛型生命周期參數(shù) 'a 來表示這三個引用的生命周期必須相同。這保證了返回的引用的生命周期與傳入的引用的生命周期一致,避免了懸垂引用。
(3) 結(jié)構(gòu)體中的生命周期,當結(jié)構(gòu)體中包含引用類型的字段時,每個引用字段都需要一個生命周期注解。
struct Excerpt<'a> {
part: &'a str,
}
這里的 Excerpt 結(jié)構(gòu)體有一個引用字段 part,為其指定了生命周期 'a。這表示 Excerpt 實例的生命周期不能超過其 part 字段引用的數(shù)據(jù)的生命周期。
(4) 生命周期省略(Elision)規(guī)則,在某些情況下,Rust 編譯器可以自動推斷生命周期,無需顯式注解,這稱為生命周期省略規(guī)則。主要有以下三條規(guī)則:
每一個引用參數(shù)都有它自己的生命周期參數(shù)。如果只有一個輸入生命周期參數(shù),那么它被賦予所有輸出生命周期參數(shù)。如果方法有 &self 或 &mut self 參數(shù),那么 self 的生命周期被賦給所有輸出生命周期參數(shù)。
(5) 靜態(tài)生命周期,'static 生命周期表示引用的數(shù)據(jù)在整個程序的運行期間都有效。字符串字面量就擁有 'static 生命周期:
let s: &'static str = "I have a static lifetime.";
(6) 生命周期約束,有時我們需要為泛型類型參數(shù)指定生命周期約束,表示類型參數(shù)中的引用必須滿足某些生命周期關(guān)系。
fn longest_with_an_announcement<'a, T>(
x: &'a str,
y: &'a str,
ann: T,
) -> &'a str
where
T: Display,
{
println!("Announcement! {}", ann);
if x.len() > y.len() {
x
} else {
y
}
}
這里我們?yōu)榉盒皖愋蛥?shù) T 指定了一個 Display Trait 約束,同時也為引用參數(shù) x 和 y 指定了生命周期 'a。
1.6 自動化測試
Rust 內(nèi)置了強大的測試支持,可以方便地編寫和運行單元測試和集成測試。Rust 的測試系統(tǒng)與語言深度集成,不需要額外的測試框架,非常易于使用。
(1) 單元測試,單元測試用于測試單個模塊的功能,通常在與被測代碼相同的文件中。
要編寫單元測試,我們需要使用 #[test] 屬性來標記測試函數(shù),并使用 assert! 或 assert_eq! 等宏來檢查測試結(jié)果。
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
這里我們在一個名為 tests 的模塊中定義了一個測試函數(shù) it_works。#[cfg(test)] 屬性表示這個模塊只在運行測試時編譯。
我們可以使用 cargo test 命令來運行單元測試。測試通過時沒有任何輸出,測試失敗時會打印相關(guān)的失敗信息。
(2) 集成測試,用于測試多個模塊間的交互,通常在專門的 tests 目錄下。
要編寫集成測試,我們需要在項目根目錄下創(chuàng)建一個 tests 目錄,并在其中創(chuàng)建測試文件。測試文件中的每個函數(shù)都是一個獨立的集成測試。
use adder;
#[test]
fn it_adds_two() {
assert_eq!(4, adder::add_two(2));
}
這里我們在 tests/integration_test.rs 文件中編寫了一個集成測試,測試了 adder 模塊的 add_two 函數(shù)。
我們可以使用 cargo test --test integration_test 命令來運行這個集成測試。
(3) 測試的組織結(jié)構(gòu),Rust 的測試系統(tǒng)支持在測試函數(shù)中使用 #[should_panic] 屬性來測試某些代碼是否會如期 panic:
#[test]
#[should_panic(expected = "Divide by zero")]
fn test_divide_by_zero() {
let _ = 1 / 0;
}
我們還可以使用 #[ignore] 屬性來暫時忽略某些測試:
#[test]
#[ignore]
fn expensive_test() {
// 耗時較長的測試代碼...
}
被忽略的測試在普通的 cargo test 運行中會被跳過,但可以使用 cargo test -- --ignored 來專門運行這些測試。
(4) 測試的最佳實踐,為了編寫出高質(zhì)量的 Rust 測試,應該遵循以下最佳實踐:
測試代碼應該簡單、可讀,避免過度復雜的邏輯。每個測試應該專注于測試一個特定的功能點,避免在一個測試中測試多個不相關(guān)的內(nèi)容。測試應該獨立運行,不應該依賴于其他測試的運行結(jié)果或順序。測試應該能夠穩(wěn)定重復運行,避免依賴于隨機性或外部環(huán)境。對于復雜的功能,應該從多個角度編寫測試,覆蓋各種可能的輸入和邊界條件。
1.7 迭代器
迭代器是 Rust 中一個非常重要和強大的概念,它提供了一種通用、高效、靈活的方式來遍歷和操作集合中的元素。Rust 的迭代器深受函數(shù)式編程的影響,支持鏈式調(diào)用、惰性求值等特性,可以顯著提高代碼的可讀性和性能。
(1) 迭代器的定義,在 Rust 中,迭代器是實現(xiàn)了 Iterator trait 的任何類型。Iterator trait 定義了一個 next 方法,用于返回迭代器的下一個元素:
pub trait Iterator {
type Item;
fn next(&mut self) -> Option
// 其他方法...
}
其中,Item 是一個關(guān)聯(lián)類型,表示迭代器產(chǎn)生的元素的類型。next 方法返回一個 Option
(2) 創(chuàng)建迭代器,可以通過調(diào)用集合類型的 iter, iter_mut 或 into_iter 方法來創(chuàng)建不同類型的迭代器:
iter 方法創(chuàng)建一個產(chǎn)生不可變引用的迭代器。iter_mut 方法創(chuàng)建一個產(chǎn)生可變引用的迭代器。into_iter 方法創(chuàng)建一個獲取集合所有權(quán)的迭代器。
let v = vec![1, 2, 3];
let iter = v.iter();
這里我們從一個 vector 創(chuàng)建了一個不可變引用的迭代器。
(3) 使用迭代器,可以使用 for 循環(huán)來遍歷迭代器中的元素:
for item in iter {
println!("{}", item);
}
這會打印出 vector 中的每一個元素。也可以使用 next 方法手動遍歷迭代器:
assert_eq!(iter.next(), Some(&1));
assert_eq!(iter.next(), Some(&2));
assert_eq!(iter.next(), Some(&3));
assert_eq!(iter.next(), None);
(4) 迭代器適配器,迭代器最強大的功能之一是它提供了大量的適配器方法,可以用于轉(zhuǎn)換和過濾迭代器。這些適配器是惰性的,只有在需要時才會執(zhí)行。常用的適配器包括:
map: 對迭代器中的每個元素應用一個函數(shù),返回一個新的迭代器。filter: 根據(jù)一個謂詞函數(shù)過濾迭代器中的元素,返回一個新的迭代器。take: 從迭代器的開頭獲取指定數(shù)量的元素,返回一個新的迭代器。skip: 跳過迭代器開頭指定數(shù)量的元素,返回一個新的迭代器。zip: 將兩個迭代器的元素配對,返回一個產(chǎn)生元組的新迭代器。
let v1: Vec
let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();
assert_eq!(v2, vec![2, 3, 4]);
這里使用 map 適配器將 v1 中的每個元素加 1,然后使用 collect 方法將結(jié)果收集到一個新的 vector 中。
(5) 消費迭代器,除了適配器,迭代器還提供了一些消費方法,用于對迭代器進行最終的計算。這些方法會真正觸發(fā)迭代器的執(zhí)行,常用的消費方法包括:
collect: 將迭代器中的元素收集到一個集合中,常用于將迭代器轉(zhuǎn)換為 vector、hashmap 等類型。sum: 對迭代器中的元素求和,返回一個匯總值。product: 對迭代器中的元素求積,返回一個匯總值。min, max: 找出迭代器中的最小或最大元素。find: 根據(jù)一個謂詞函數(shù)查找迭代器中的第一個元素,返回一個 Option。fold: 使用一個初始值和一個累加函數(shù)對迭代器中的元素進行歸約,返回一個最終值。
let v1: Vec
let v1_sum: i32 = v1.iter().sum();
assert_eq!(v1_sum, 6);
這里使用 sum 方法對 v1 中的元素求和,得到最終結(jié)果 6。
1.8 文檔化注釋
文檔注釋是 Rust 中一種特殊的注釋,它們可以用于生成項目的 API 文檔。Rust 的文檔注釋支持 Markdown 語法,可以方便地編寫富文本格式的文檔。Rust 編譯器和 Cargo 工具對文檔注釋有內(nèi)置的支持,可以自動提取文檔注釋并生成美觀、實用的 HTML 格式文檔。
(1) 文檔注釋的語法,Rust 中的文檔注釋以三個斜杠 /// 開頭,可以放在 crate、模塊、函數(shù)、類型、trait、結(jié)構(gòu)體、枚舉等項的前面。
/// This is a documentation comment for a function.
///
/// It can have multiple lines and use **Markdown** syntax.
fn my_function() {
// ...
}
對于多行文檔注釋,通常在第一行寫一個簡要的概述,然后用一個空行分隔詳細的描述。
除了 ///,Rust 還支持以 //! 開頭的文檔注釋,它們用于描述包含項(如 crate、模塊)而不是被包含項。
(2) 文檔注釋中的 Markdown 語法,Rust 的文檔注釋支持常用的 Markdown 語法,包括:
(1) *斜體*和**粗體**
(2) `代碼`和```代碼塊```
(3) [鏈接](https://www.rust-lang.org/)
(4) 標題、列表、引用等
/// # Examples
///
/// ```
/// let result = my_function(42);
/// assert_eq!(result, Some(42));
/// ```
fn my_function(x: i32) -> Option
Some(x)
}
這里我們在文檔注釋中使用了二級標題和代碼塊來提供一個使用示例。
(3) 文檔測試,Rust 支持在文檔注釋中編寫可執(zhí)行的測試代碼,稱為文檔測試(Doc-tests)。文檔測試可以確保文檔中的示例代碼是正確和最新的。要編寫文檔測試,只需在文檔注釋的代碼塊中編寫普通的 Rust 代碼和斷言即可。
/// ```
/// let result = my_function(42);
/// assert_eq!(result, Some(42));
/// ```
fn my_function(x: i32) -> Option
Some(x)
}
可以使用 cargo test 命令來運行文檔測試,就像運行普通的單元測試一樣。
(4) 生成 API 文檔,可以使用 cargo doc 命令來生成項目的 API 文檔。這個命令會提取所有的文檔注釋,并生成一個 HTML 格式的文檔網(wǎng)站。在項目根目錄下運行:
cargo doc --open
這會在 target/doc 目錄下生成文檔,并自動在瀏覽器中打開文檔的首頁。
生成的文檔網(wǎng)站包括了模塊、類型、函數(shù)等項的詳細信息,以及它們的文檔注釋。文檔中的代碼塊會被高亮顯示,Markdown 語法會被正確渲染。文檔網(wǎng)站還提供了搜索、導航等功能,方便用戶查找和瀏覽 API。
(5) 常用的文檔注釋慣例,為了編寫出高質(zhì)量的 API 文檔,應該遵循一些常用的文檔注釋慣例:
在文檔注釋的第一行提供一個簡潔的概述,總結(jié)項的功能或目的。對于函數(shù),描述它的參數(shù)、返回值和可能的錯誤情況。對于類型,描述它的屬性、方法和使用場景。提供具體的使用示例,最好是可以直接運行的代碼。使用 Markdown 語法來組織和格式化文檔,提高可讀性。保持文檔的簡潔、準確和最新,避免冗余或過時的信息。
文檔注釋是 Rust 項目的重要組成部分,它們不僅提供了 API 的使用指南,也體現(xiàn)了項目的設(shè)計思路和質(zhì)量。作為 Rust 程序員,應該重視文檔注釋的編寫,將其作為開發(fā)過程的一部分,而不是事后的補充。好的文檔注釋可以顯著提高項目的可用性和可維護性,吸引更多的用戶和貢獻者。
1.9 Box智能指針
Box 是 Rust 標準庫提供的一種智能指針類型,它允許我們在堆上分配值并通過指針來操作它們。Box 通常用于以下場景:
當我們需要在堆上分配一個值,而不是在棧上。當我們需要一個指向 trait 對象的指針。當一個值的大小在編譯時無法確定,但我們需要一個固定大小的值時。
(1) 創(chuàng)建 Box,可以使用 Box::new 函數(shù)來創(chuàng)建一個 Box 指針:
let x = Box::new(5);
這會在堆上分配一個值 5,并返回一個指向該值的 Box 指針。Box 指針的類型是 Box
(2) 解引用 Box,可以使用解引用操作符 * 來訪問 Box 指針指向的值:
let x = Box::new(5);
assert_eq!(*x, 5);
這會返回 Box 指向的值的引用。
(3) Box 與所有權(quán),Box 與普通指針不同,它擁有指向的值的所有權(quán)。當 Box 指針離開作用域時,它指向的值也會被自動釋放。這避免了手動內(nèi)存管理的需要。
{
let x = Box::new(5);
} // x 離開作用域,它指向的值被釋放
(4) Box 與 trait 對象,Box 經(jīng)常用于創(chuàng)建指向 trait 對象的指針。trait 對象允許我們在運行時使用動態(tài)分發(fā),對不同的類型進行抽象。
trait Draw {
fn draw(&self);
}
struct Circle;
impl Draw for Circle {
fn draw(&self) {
println!("Drawing a circle");
}
}
struct Square;
impl Draw for Square {
fn draw(&self) {
println!("Drawing a square");
}
}
fn draw_shapes(shapes: Vec
for shape in shapes {
shape.draw();
}
}
fn main() {
let shapes: Vec
Box::new(Circle),
Box::new(Square),
];
draw_shapes(shapes);
}
這里我們定義了一個 Draw trait,然后為 Circle 和 Square 實現(xiàn)了該 trait。在 draw_shapes 函數(shù)中,我們使用 Box
(6) Deref 和 Drop trait,Box 之所以能夠像普通引用一樣使用解引用操作符 *,是因為它實現(xiàn)了 Deref trait。Deref trait 允許我們自定義解引用行為。
同時,Box 也實現(xiàn)了 Drop trait,這使得它在離開作用域時能夠自動釋放所指向的值。
除了 Box,Rust 標準庫還提供了其他一些智能指針類型,如 Rc(引用計數(shù))、Arc(原子引用計數(shù))、RefCell(運行時借用檢查)等。它們各自有不同的特點和適用場景。
1.10 RC智能指針
Rc 是 Rust 標準庫提供的一種智能指針類型,它允許多個所有者共享同一個值的所有權(quán)。當我們需要在堆上分配一個值,并且這個值可能被多個所有者共享時,就可以使用 Rc。
(1) 創(chuàng)建 Rc,可以使用 Rc::new 函數(shù)來創(chuàng)建一個 Rc 指針:
use std::rc::Rc;
let x = Rc::new(5);
這會在堆上分配一個值 5,并返回一個指向該值的 Rc 指針。Rc 指針的類型是 Rc
(2) 克隆 Rc,可以使用 clone 方法來創(chuàng)建一個 Rc 指針的拷貝:
let x = Rc::new(5);
let y = x.clone();
這會創(chuàng)建一個新的 Rc 指針 y,它與 x 共享同一個值的所有權(quán)。此時,該值的引用計數(shù)會加 1。
(3) Rc 與引用計數(shù),Rc 內(nèi)部維護了一個引用計數(shù),用于跟蹤當前有多少個 Rc 指針指向同一個值。當一個新的 Rc 指針被創(chuàng)建時(通過 Rc::new 或 clone),引用計數(shù)會加 1;當一個 Rc 指針離開作用域時,引用計數(shù)會減 1。當引用計數(shù)為 0 時,說明沒有任何 Rc 指針指向該值,該值會被自動釋放。
這種引用計數(shù)的機制允許多個所有者安全地共享同一個值,而無需手動管理內(nèi)存。
(4) Rc 與不可變借用,Rc 指針只能提供對值的不可變借用(&T),而不能提供可變借用(&mut T)。這是因為 Rc 允許多個指針共享同一個值,如果同時有多個可變借用,就會違反 Rust 的借用規(guī)則(在任意時刻,要么只能有一個可變借用,要么只能有多個不可變借用)。
如果我們需要在 Rc 指針之間共享可變狀態(tài),可以將 Rc 與 RefCell 結(jié)合使用(Rc
(5) Rc 的性能開銷,雖然 Rc 提供了方便的引用計數(shù)機制,但它也引入了一些性能開銷:
每次創(chuàng)建或克隆 Rc 指針時,都需要更新引用計數(shù),這會帶來一定的時間開銷。Rc 指針本身也有一定的空間開銷,因為它需要存儲引用計數(shù)和其他元數(shù)據(jù)。Rc 的引用計數(shù)更新需要原子操作,這在多線程環(huán)境下可能會導致性能瓶頸。
因此,在性能關(guān)鍵的場景下,我們應該謹慎使用 Rc,盡可能使用普通的引用或所有權(quán)轉(zhuǎn)移。
(6) Rc 與循環(huán)引用,雖然 Rc 可以安全地共享所有權(quán),但它無法處理循環(huán)引用的情況。如果兩個 Rc 指針相互引用,形成了一個循環(huán),那么它們的引用計數(shù)將永遠無法降為 0,從而導致內(nèi)存泄漏。
為了解決這個問題,Rust 提供了 Weak 指針(Weak
1.11 RefCell智能指針
Rust 編程語言提供了一種獨特的智能指針類型RefCell,它在 Rust 的所有權(quán)和借用規(guī)則之上提供了一層動態(tài)借用檢查機制。RefCell 允許在運行時對數(shù)據(jù)進行可變和不可變借用,而不是在編譯時進行嚴格的借用檢查。
RefCell 的主要特點如下:
動態(tài)借用檢查:與 Rust 的默認借用規(guī)則不同,RefCell 在運行時檢查借用規(guī)則。這意味著借用規(guī)則的違反會導致運行時的 panic,而不是編譯時錯誤。 內(nèi)部可變性:RefCell 提供了內(nèi)部可變性,允許在不可變引用的情況下修改數(shù)據(jù)。這對于某些特定的場景非常有用,例如在不可變數(shù)據(jù)結(jié)構(gòu)中存儲可變數(shù)據(jù)。 單線程使用:RefCell 只適用于單線程環(huán)境。在多線程環(huán)境中,應該使用 Mutex 或其他同步原語來確保數(shù)據(jù)的安全訪問。 借用規(guī)則:盡管 RefCell 在運行時檢查借用規(guī)則,但它仍然遵循 Rust 的借用規(guī)則。在任何給定時間,只能擁有一個可變引用或任意數(shù)量的不可變引用,但不能同時擁有兩者。
RefCell 通過提供 borrow 和 borrow_mut 方法來獲取對內(nèi)部數(shù)據(jù)的不可變和可變引用。這些方法在運行時執(zhí)行借用規(guī)則檢查,如果違反規(guī)則,就會觸發(fā) panic。
以下是一個簡單的 RefCell 示例:
use std::cell::RefCell;
let cell = RefCell::new(5);
let mut borrowed_value = cell.borrow_mut();
*borrowed_value += 1;
println!("Value: {:?}", cell.borrow());
在這個例子中,我們創(chuàng)建了一個包含整數(shù) 5 的 RefCell。然后,我們使用 borrow_mut 方法獲取一個可變引用,并修改內(nèi)部值。最后,我們使用 borrow 方法獲取一個不可變引用并打印修改后的值。
總之,RefCell 提供了一種靈活的方式來處理 Rust 中的借用規(guī)則,允許在特定場景下進行動態(tài)借用檢查和內(nèi)部可變性。它是 Rust 標準庫中的重要工具,用于處理某些復雜的所有權(quán)和借用情況。
1.12 多線程編程
Rust 提供了多種工具和機制來支持并發(fā)編程和多線程。
(1) 線程(Threads):Rust 通過標準庫中的 std::thread 模塊提供了創(chuàng)建和管理線程的功能。每個線程都有自己的堆棧,并可以并發(fā)地執(zhí)行代碼。通過調(diào)用 thread::spawn 函數(shù),可以創(chuàng)建一個新的線程,并在其中執(zhí)行閉包或函數(shù)。
(2) 消息傳遞(Message Passing):Rust 鼓勵使用消息傳遞的方式進行線程間通信,而不是共享內(nèi)存。標準庫提供了 std::sync::mpsc 模塊,其中 mpsc 代表 “多個生產(chǎn)者,單個消費者”(multiple producers, single consumer)。通過創(chuàng)建通道(channel),一個線程可以將消息發(fā)送到通道的發(fā)送端,而另一個線程可以從通道的接收端接收消息。這種方式可以避免共享內(nèi)存導致的數(shù)據(jù)競爭和其他并發(fā)問題。
(3) 互斥器(Mutexes):當多個線程需要訪問共享數(shù)據(jù)時,Rust 提供了互斥器(Mutex)來確保數(shù)據(jù)的互斥訪問?;コ馄魇且环N鎖機制,保證在同一時刻只有一個線程可以訪問被保護的數(shù)據(jù)。Rust 的 std::sync::Mutex 類型實現(xiàn)了互斥器,通過 lock 方法獲取鎖,并在鎖的生命周期結(jié)束時自動釋放鎖。
(4) 原子引用計數(shù)(Arc):Arc(Atomic Reference Counting)是一種線程安全的引用計數(shù)智能指針。它允許多個線程共享同一份數(shù)據(jù)的所有權(quán)。當多個線程需要讀取共享數(shù)據(jù)時,可以使用 Arc 來確保數(shù)據(jù)的安全性。Arc 通過原子操作來增加和減少引用計數(shù),以跟蹤數(shù)據(jù)的所有者數(shù)量。當引用計數(shù)降為零時,數(shù)據(jù)將被自動釋放。
(5) 條件變量(Condvar):條件變量提供了一種機制,允許線程在某個條件滿足之前進入等待狀態(tài),并在條件滿足時被喚醒。Rust 的 std::sync::Condvar 類型與互斥器配合使用,用于線程間的同步和等待。線程可以在互斥器上調(diào)用 wait 方法進入等待狀態(tài),直到另一個線程調(diào)用 notify_one 或 notify_all 方法喚醒等待的線程。
(6) 讀寫鎖(RwLock):讀寫鎖是一種特殊的鎖機制,允許多個讀者同時訪問數(shù)據(jù),但只允許一個寫者獨占訪問數(shù)據(jù)。這對于頻繁讀取但較少寫入的場景非常有用,可以提高并發(fā)性能。Rust 的 std::sync::RwLock 類型實現(xiàn)了讀寫鎖,通過 read 方法獲取讀鎖,通過 write 方法獲取寫鎖。
(7) Send trait,表示一個類型可以安全地在線程間傳遞所有權(quán)。如果一個類型實現(xiàn)了 Send trait,就意味著它可以在不同的線程間移動,而不會導致任何數(shù)據(jù)競爭或不安全的行為。換句話說,實現(xiàn)了 Send trait 的類型可以跨線程boundaries安全地傳遞。在 Rust 中,大多數(shù)基本類型都實現(xiàn)了 Send trait,例如原始類型、標準庫中的大部分集合類型以及一些同步原語(如 Mutex 和 Arc)。對于自定義類型,如果其所有字段都實現(xiàn)了 Send trait,那么該類型自動實現(xiàn) Send trait。
(8) Sync trait,表示一個類型可以安全地在多個線程間共享引用。如果一個類型實現(xiàn)了 Sync trait,就意味著對該類型的引用可以在不同的線程間共享,而不會導致任何數(shù)據(jù)競爭或不安全的行為。與 Send trait 類似,Rust 中的大多數(shù)基本類型和標準庫中的許多類型都實現(xiàn)了 Sync trait。對于自定義類型,如果其所有字段都實現(xiàn)了 Sync trait,那么該類型自動實現(xiàn) Sync trait。
Send 和 Sync trait 在 Rust 的多線程并發(fā)中扮演著重要的角色。它們提供了一種機制,讓編譯器在編譯時檢查類型的線程安全性。
當在多個線程間傳遞數(shù)據(jù)時,Rust 編譯器會自動檢查類型是否實現(xiàn)了 Send trait。如果一個類型沒有實現(xiàn) Send trait,那么在嘗試將其跨線程傳遞時,編譯器會產(chǎn)生錯誤。這有助于在編譯時捕獲潛在的線程安全問題。
類似地,當在多個線程間共享引用時,Rust 編譯器會檢查類型是否實現(xiàn)了 Sync trait。如果一個類型沒有實現(xiàn) Sync trait,那么在嘗試在線程間共享其引用時,編譯器會產(chǎn)生錯誤。
1.13 模式匹配
Rust 中的模式匹配是一個強大的特性,它允許你根據(jù)數(shù)據(jù)的結(jié)構(gòu)和值來解構(gòu)和匹配數(shù)據(jù)。模式匹配廣泛應用于變量綁定、函數(shù)參數(shù)、控制流結(jié)構(gòu)等方面。
(1) 模式(Patterns):模式是一種用于匹配和解構(gòu)數(shù)據(jù)的語法結(jié)構(gòu)。它可以是字面值、變量、通配符、元組、結(jié)構(gòu)體、枚舉等。模式用于描述數(shù)據(jù)的形狀和結(jié)構(gòu),并允許你提取感興趣的部分。
(2) 匹配表達式(Match Expressions):匹配表達式是 Rust 中最常見的模式匹配形式。它使用 match 關(guān)鍵字,后跟一個表達式和一系列的模式匹配分支(arms)。每個分支包含一個模式和相應的代碼塊。當表達式的值與某個模式匹配時,相應的代碼塊會被執(zhí)行。
match expression {
pattern1 => {
// 代碼塊 1
},
pattern2 => {
// 代碼塊 2
},
_ => {
// 默認情況下的代碼塊
},
}
(3) 解構(gòu)(Destructuring): 模式匹配允許你解構(gòu)復雜的數(shù)據(jù)類型,如元組、結(jié)構(gòu)體和枚舉,并提取其中的值。通過在模式中指定數(shù)據(jù)結(jié)構(gòu)的字段或變量名,可以將數(shù)據(jù)解構(gòu)為獨立的部分。
let tuple = (1, "hello", true);
let (x, y, z) = tuple;
(4) 綁定(Bindings): 模式匹配可以將匹配的值綁定到變量上,以便在相應的代碼塊中使用。通過在模式中使用變量名,可以捕獲匹配的值并在后續(xù)的代碼中引用它們。
let (x, y) = (1, 2);
println!("x = {}, y = {}", x, y);
(5) 通配符(Wildcards): 通配符 _ 用于匹配任意值,而不將其綁定到變量。當你不關(guān)心某個值或者想忽略某些部分時,可以使用通配符。
let (_, y) = (1, 2);
(6) 守衛(wèi)(Guards):守衛(wèi)是一個附加的條件,用于進一步過濾模式匹配的結(jié)果。守衛(wèi)使用 if 關(guān)鍵字,后跟一個布爾表達式。只有當模式匹配成功且守衛(wèi)條件為真時,相應的代碼塊才會被執(zhí)行。
match x {
Some(value) if value > 0 => println!("Positive value: {}", value),
Some(value) if value < 0 => println!("Negative value: {}", value),
Some(0) => println!("Zero"),
None => println!("No value"),
}
(7) 枚舉匹配(Enum Matching):模式匹配在處理枚舉類型時非常有用??梢允褂媚J狡ヅ鋪砥ヅ涿杜e的不同變體,并提取其中的關(guān)聯(lián)值。
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
match message {
Message::Quit => println!("Quit"),
Message::Move { x, y } => println!("Move to ({}, {})", x, y),
Message::Write(text) => println!("Text message: {}", text),
Message::ChangeColor(r, g, b) => println!("Change color to RGB({}, {}, {})", r, g, b),
}
(8) 綁定模式(Binding Patterns):綁定模式允許你在模式匹配時為整個模式或部分模式創(chuàng)建變量綁定。通過在模式前面添加 @ 符號,可以創(chuàng)建一個綁定,并在后續(xù)的代碼塊中使用該綁定。
match person {
Person { name: name @ "Alice", age } => println!("{} is {} years old", name, age),
Person { name, age } => println!("{} is {} years old", name, age),
}
(9) 范圍模式(Range Patterns):范圍模式允許你匹配一個范圍內(nèi)的值??梢允褂?..= 語法來指定一個包含上下界的閉區(qū)間,或者使用 .. 語法來指定一個不包含上界的半開區(qū)間。
match x {
0..=10 => println!("Between 0 and 10"),
11..=20 => println!("Between 11 and 20"),
_ => println!("Greater than 20"),
}
(10) 多模式(Multiple Patterns):可以使用 | 符號將多個模式組合在一起,形成一個多模式。如果值與任何一個模式匹配,相應的代碼塊就會被執(zhí)行。
match x {
1 | 2 => println!("One or two"),
3..=5 => println!("Between 3 and 5"),
_ => println!("Something else"),
}
柚子快報激活碼778899分享:Rust(2)進階語法
參考鏈接
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。