Rust太難?那是你沒看到這套Rust語言語言學(xué)習(xí)總結(jié)(上)
一、Rust開發(fā)環(huán)境指南

1.1 Rust代碼執(zhí)行
根據(jù)編譯原理知識,編譯器不是直接將源語言翻譯為目標(biāo)語言,而是翻譯為一種“中間語言”,編譯器從業(yè)人員稱之為“IR”--指令集,之后再由中間語言,利用后端程序和設(shè)備翻譯為目標(biāo)平臺的匯編語言。
Rust代碼執(zhí)行:
1)? ?Rust代碼經(jīng)過分詞和解析,生成AST(抽象語法樹)。
2)? ? 然后把AST進一步簡化處理為HIR(High-level IR),目的是讓編譯器更方便的做類型檢查。
3)? ?HIR會進一步被編譯為MIR(Middle IR),這是一種中間表示,主要目的是:
a)縮短編譯時間;
b)縮短執(zhí)行時間;
c)更精確的類型檢查。
4)?????????最終MIR會被翻譯為LLVM IR,然后被LLVM的處理編譯為能在各個平臺上運行的目標(biāo)機器碼。
IR:中間語言
HIR:高級中間語言
MIR:中級中間語言
LLVM?:Low Level Virtual Machine,底層虛擬機。
LLVM是構(gòu)架
編譯器
(compiler)的框架系統(tǒng),以C++編寫而成,用于優(yōu)化以任意程序語言編寫的程序的編譯時間(compile-time)、鏈接時間(link-time)、運行時間(run-time)以及空閑時間(idle-time)
無疑,不同編譯器的中間語言IR是不一樣的,而IR可以說是集中體現(xiàn)了這款編譯器的特征:他的算法,優(yōu)化方式,匯編流程等等,想要完全掌握某種編譯器的工作和運行原理,分析和學(xué)習(xí)這款編譯器的中間語言無疑是重要手段。
由于中間語言相當(dāng)于一款編譯器前端和后端的“橋梁”,如果我們想進行基于LLVM的后端移植,無疑需要開發(fā)出對應(yīng)目標(biāo)平臺的編譯器后端,想要順利完成這一工作,透徹了解LLVM的中間語言無疑是非常必要的工作。
LLVM相對于gcc的一大改進就是大大提高了中間語言的生成效率和可讀性,?LLVM的中間語言是一種介于c語言和匯編語言的格式,他既有高級語言的可讀性,又能比較全面地反映計算機底層數(shù)據(jù)的運算和傳輸?shù)那闆r,精煉而又高效。
1.1.1??MIR
MIR是基于控制流圖(Control Flow Graph,CFG)的抽象數(shù)據(jù)結(jié)構(gòu),它用有向圖(DAG)形式包含了程序執(zhí)行過程中所有可能的流程。所以將基于MIR的借用檢查稱為非詞法作用域的生命周期。
MIR由一下關(guān)鍵部分組成:
l??基本塊(Basic block,bb),他是控制流圖的基本單位,
???語句(statement)
? ??終止句(Terminator)
l??本地變量,占中內(nèi)存的位置,比如函數(shù)參數(shù)、局部變量等。
l??位置(Place),在內(nèi)存中標(biāo)識未知的額表達(dá)式。
l??右值(RValue),產(chǎn)生值的表達(dá)式。
具體的工作原理見《Rust編程之道》的第158和159頁。
可以在play.runst-lang.org中生成MIR代碼。
1.1?Rust安裝
???方法一:見Rust官方的
installation章節(jié)
介紹。
實際上就是調(diào)用該命令來安裝即可:curl https://sh.rustup.rs -sSf | sh
???方法二:下載離線的安裝包來安裝,具體的可見Rust官方的
Other Rust Installation Methods章節(jié)
。
1.2 Rust編譯&運行
1.2.1 Cargo包管理
Cargo是Rust中的包管理工具,第三方包叫做crate。
Cargo一共做了四件事:
l??使用兩個元數(shù)據(jù)(metadata)文件來記錄各種項目信息
l??獲取并構(gòu)建項目的依賴關(guān)系
l??使用正確的參數(shù)調(diào)用rustc或其他構(gòu)建工具來構(gòu)建項目
l??為Rust生態(tài)系統(tǒng)開發(fā)建議了統(tǒng)一標(biāo)準(zhǔn)的工作流
Cargo文件:
Cargo.lock:只記錄依賴包的詳細(xì)信息,不需要開發(fā)者維護,而是由Cargo自動維護
Cargo.toml:描述項目所需要的各種信息,包括第三方包的依賴
cargo編譯默認(rèn)為Debug模式,在該模式下編譯器不會對代碼進行任何優(yōu)化。也可以使用--release參數(shù)來使用發(fā)布模式。release模式,編譯器會對代碼進行優(yōu)化,使得編譯時間變慢,但是代碼運行速度會變快。
官方編譯器rustc,負(fù)責(zé)將rust源碼編譯為可執(zhí)行的文件或其他文件(.a、.so、.lib等)。例如:rustc box.rs
Rust還提供了包管理器Cargo來管理整個工作流程。例如:
cargo new?first_pro_create?:創(chuàng)建名為first_pro_create的項目
cargo new --lib?first_lib_create?:創(chuàng)建命令first_lib_create的庫項目
cargo doc
lcargo doc --open
cargo test
cargo test -- --test-threads=1
cargo build
cargo build --release
cargo run
cargo install --path
cargo uninstall first_pro_create
cargo new –bin use_regex
1.2.2?使用第三方包
Rust可以在Cargo.toml中的[dependencies]下添加想依賴的包來使用第三方包。
然后在src/main.rs或src/lib.rs文件中,使用extern crate命令聲明引入該包即可使用。
例如:
值得注意的是,使用extern crate聲明包的名稱是linked_list,用的是下劃線“_”,而在Cargo.toml中用的是連字符“-”。其實Cargo默認(rèn)會把連字符轉(zhuǎn)換成下劃線。
Rust也不建議以“-rs”或“_rs”為后綴來命名包名,而且會強制性的將此后綴去掉。
具體的見《Rust編程之道》的第323頁。
1.4 Rust常用命令
1.5 Rust命令規(guī)范
函數(shù):?????蛇形命名法(snake_case),例如:func_name()
文件名:?蛇形命名法(snake_case),例如file_name.rs、main.rs
臨時變量名:蛇形命名法(snake_case)
全局變量名:
結(jié)構(gòu)體:???大駝峰命名法,例如:struct FirstName { name: String}
enum類型: ?大駝峰命名法。
關(guān)聯(lián)常量:常量名必須全部大寫。什么是關(guān)聯(lián)常量見《Rust編程之道》的第221頁。
Cargo默認(rèn)會把連字符“-”轉(zhuǎn)換成下劃線“_”。
Rust也不建議以“-rs”或“_rs”為后綴來命名包名,而且會強制性的將此后綴去掉。
二、Rust語法
2.1?疑問&總結(jié)
2.1.1 Copy語義?&& Move語義(Move語義必須轉(zhuǎn)移所有權(quán))
類型越來越豐富,值類型和引用類型難以描述全部情況,所以引入了:
值語義(Value Semantic)
復(fù)制以后,兩個數(shù)據(jù)對象擁有的存儲空間是獨立的,互不影響。
基本的原生類型都是值語義,這些類型也被稱為POD(Plain old data)。POD類型都是值語義,但是值語義類型并不一定都是POD類型。
具有值語義的原生類型,在其作為右值進行賦值操作時,編譯器會對其進行按位復(fù)制。
引用語義(Reference Semantic)
復(fù)制以后,兩個數(shù)據(jù)對象互為別名。操作其中任意一個數(shù)據(jù)對象,則會影響另外一個。
智能指針Box
引用語義類型不能實現(xiàn)Copy,但可以實現(xiàn)Clone的clone方法,以實現(xiàn)深復(fù)制。
在Rust中,可以通過是否實現(xiàn)Copy trait來區(qū)分?jǐn)?shù)據(jù)類型的值語義和引用語義。但為了更加精準(zhǔn),Rust也引用了新的語義:復(fù)制(Copy)語義和移動(Move)語義。
Copy語義:對應(yīng)值語義,即實現(xiàn)了Copy的類型在進行按位復(fù)制時是安全的。
Move語義:對應(yīng)引用語義。在Rust中不允許按位復(fù)制,只允許移動所有權(quán)。
2.1.2?哪些實現(xiàn)了Copy
結(jié)構(gòu)體?:當(dāng)成員都是復(fù)制語義類型時,不會自動實現(xiàn)Copy。
枚舉體?:當(dāng)成員都是復(fù)制語義類型時,不會自動實現(xiàn)Copy。
結(jié)構(gòu)體?&&?枚舉體:
1) ?所有成員都是復(fù)制語義類型時,需要添加屬性#[derive(Debug,Copy,Clone)]來實現(xiàn)Copy。
2) ?如果有移動語義類型的成員,則無法實現(xiàn)Copy。
元組類型?:本身實現(xiàn)了Copy。如果元素均為復(fù)制語義類型,則默認(rèn)是按位復(fù)制,否則執(zhí)行移動語義。
字符串字面量?&str:?支持按位復(fù)制。例如:c = “hello”;?則c就是字符串字面量。
2.1.3?哪些未實現(xiàn)Copy
字符串對象String?:to_string()?可以將字符串字面量轉(zhuǎn)換為字符串對象。
2.1.4?哪些實現(xiàn)了Copy trait
原生整數(shù)類型
對于實現(xiàn)Copy的類型,其clone方法只需要簡單的實現(xiàn)按位復(fù)制即可。
2.1.5?哪些未實現(xiàn)Copy trait
Box
實現(xiàn)了Copy trait,有什么作用?
實現(xiàn)Copy trait的類型同時擁有復(fù)制語義,在進行賦值或者傳入函數(shù)等操作時,默認(rèn)會進行按位復(fù)制。
對于默認(rèn)可以安全的在棧上進行按位復(fù)制的類型,就只需要按位復(fù)制,也方便管理內(nèi)存。
對于默認(rèn)只可在堆上存儲的數(shù)據(jù),必須進行深度復(fù)制。深度復(fù)制需要在堆內(nèi)存中重新開辟空間,這會帶來更多的性能開銷。
2.1.6?哪些是在棧上的?哪些是在堆上的?
2.1.7 let綁定
Rust聲明的綁定默認(rèn)為不可變。
如果需要修改,可以用mut來聲明綁定是可變的。
2.2?數(shù)據(jù)類型
很多編程語言中的數(shù)據(jù)類型是分為兩類:
值類型
一般是指可以將數(shù)據(jù)都保存在同一位置的類型。例如數(shù)值、布爾值、結(jié)構(gòu)體等都是值類型。
值類型有:
原生類型
結(jié)構(gòu)體
枚舉體
引用類型
會存在一個指向?qū)嶋H存儲區(qū)的指針。比如通常一些引用類型會將數(shù)據(jù)存儲在堆中,而棧中只存放指向堆中數(shù)據(jù)的地址(指針)。
引用類型有:
普通引用類型
原生指針類型
2.2.1?基本數(shù)據(jù)類型
布爾類型
bool類型只有兩個值:true和false。
基本數(shù)字類型
主要關(guān)注取值范圍,具體的見《Rust編程之道》的第26頁。
字符類型
用單引號來定義字符(char)類型。字符類型代表一個Unicode標(biāo)量值,每個字節(jié)占4個字節(jié)。
數(shù)組類型
數(shù)組的類型簽名為[T; N]。T是一個泛型標(biāo)記,代表數(shù)組中元素的某個具體類型。N代表數(shù)組長度,在編譯時必須確定其值。
數(shù)組特點:
大小固定
元素均為同類型
默認(rèn)不可變
切片類型
切片(Slice)類型是對一個數(shù)組的引用片段。在底層,切片代表一個指向數(shù)組起始位置的指針和數(shù)組長度。用[T]類型表示連續(xù)序列,那么切片類型就是&[T]和&mut[T]。
具體的見《Rust編程之道》的第30頁。
str字符串類型
字符串類型str,通常是以不可變借用的形式存在,即&str(字符串切片)。
Rust將字符串分為兩種:
1)? ?&str?:固定長度字符串
2) ?String?:可以隨意改變其長度。
&str字符串類型由兩部分組成:
1) 指向字符串序列的指針;
2) 記錄長度的值。
&str存儲于棧上,str字符串序列存儲于程序的靜態(tài)只讀數(shù)據(jù)段或者堆內(nèi)存中。
&str是一種胖指針。
never類型
never類型,即!。該類型用于表示永遠(yuǎn)不可能有返回值的計算類型。
其他(此部分不屬于基本數(shù)據(jù)類型)
此部分不屬于基本數(shù)據(jù)類型,由于編排問題,暫時先放在此處。
胖指針
胖指針:包含了動態(tài)大小類型地址信息和攜帶了長度信息的指針。
具體的見《Rust編程之道》的第54頁。
零大小類型
零大小類型(Zero sized Type,ZST)的特點是:它們的值就是其本身,運行時并不占用內(nèi)存空間。
單元類型和單元結(jié)構(gòu)體大小為零,由單元類型組成的數(shù)組大小也是零。
ZST類型代表的意義是“空”。
底類型
底類型其實是介紹過的never類型,用嘆號(!)表示。它的特點是:
沒有值
是其他任意類型的子類型
如果說ZST類型表示“空”的話,那么底類型就表示“無”。
底類型無值,而且它可以等價于任意類型。
具體的見《Rust編程之道》的第57頁。
2.2.2?復(fù)合數(shù)據(jù)類型
元組
Rust提供了4中復(fù)合數(shù)據(jù)類型:
元組(Tuple)
結(jié)構(gòu)體(Struct)
枚舉體(Enum)
聯(lián)合體(Union)
先來介紹元組。元組是一種異構(gòu)有限序列,形如(T,U,M,N)。所謂異構(gòu),就是指元組內(nèi)的元素可以是不同類型。所謂有限,是指元組有固定的長度。
空元組:?()
只有一個值時,需要加逗號:?(0,)
結(jié)構(gòu)體
Rust提供了3中結(jié)構(gòu)體:
具名結(jié)構(gòu)體
元組結(jié)構(gòu)體
單元結(jié)構(gòu)體
例如:
具名結(jié)構(gòu)體:
struct People {
name: &’static str,
}
元組結(jié)構(gòu)體:字段沒有名稱,只有類型:
struct Color(i32, i32, i32);
當(dāng)一個元組結(jié)構(gòu)體只有一個字段的時候,稱為New Type模式。例如:
struct Integer(u32);
單元結(jié)構(gòu)體:沒有任何字段的結(jié)構(gòu)體。單元結(jié)構(gòu)體實例就是其本身。
struct Empty;
結(jié)構(gòu)體更新語法
使用Struct更新語法(..)從其他實例創(chuàng)建新實例。當(dāng)新實例使用舊實例的大部分值時,可以使用struct update語法。?例如:
#[derive(Debug,Copy,Clone)]
struct Book<’a> {
name: &’a str,
isbn:? i32,
version: i32,
}
let book = Book {
name: “Rust編程之道”,? isbn: 20181212, version: 1
};
let book2 = Book {version: 2,?..book};
注:
l??如果結(jié)構(gòu)體使用了移動語義的成員字段,則不允許實現(xiàn)Copy。
l??Rust不允許包含了String類型字段的結(jié)構(gòu)體實現(xiàn)Copy。
l??更新語法會轉(zhuǎn)移字段的所有權(quán)。
枚舉體
該類型包含了全部可能的情況,可以有效的防止用戶提供無效值。例如:
enum Number {
Zero,
One,
}
Rust還支持?jǐn)y帶類型參數(shù)的枚舉體。這樣的枚舉值本質(zhì)上屬于函數(shù)類型,他可以通過顯式的指定類型來轉(zhuǎn)換為函數(shù)指針類型。例如:
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
枚舉體在Rust中屬于非常重要的類型之一。例如:Option枚舉類型。
聯(lián)合體
2.2.3?常用集合類型
線性序列:向量
在Rust標(biāo)準(zhǔn)庫std::collections模塊下有4中通用集合類型,分別如下:
線性序列:向量(Vec)、雙端隊列(VecDeque)、鏈表(LinkedList)
Key-Value映射表:無序哈希表(HashMap)、有序映射表(BTreeMap)
集合類型:無序集合(HashSet)、有序集合(BTreeSet)
優(yōu)先隊列:二叉堆(BinaryHeap)
具體的見《Rust編程之道》的第38頁和271頁。
向量也是一種數(shù)組,和基本數(shù)據(jù)類型中的數(shù)組的區(qū)別在于:向量可動態(tài)增長。
示例:
let mut v1 =?vec![];
let mut v2 =?vec![0; 10];
let mut v3 =?Vec::new();
vec!是一個宏,用來創(chuàng)建向量字面量。
線性序列:雙端隊列
雙端隊列(Double-ended Queue,縮寫Deque)是一種同時具有隊列(先進先出)和棧(后進先出)性質(zhì)的數(shù)據(jù)結(jié)構(gòu)。
雙端隊列中的元素可以從兩端彈出,插入和刪除操作被限定在隊列的兩端進行。
示例:
use std::collections::VecDeque;
let mut buf =?VecDeque::new();
buf.push_front(1);
buf.get(0);
buf.push_back(2);
線性序列:鏈表
Rust提供的鏈表是雙向鏈表,允許在任意一端插入或彈出元素。最好使用Vec或VecDeque類型,他們比鏈表更加快速,內(nèi)存訪問效率更高。
示例:
use std::collections::LinkedList;
let mut list = LinkedList::new();
list.push_front(‘a(chǎn)’);
list.append(&mut list2);
list.push_back(‘b’);
Key-Value映射表:HashMap和BTreeMap
l??HashMap
l??BTreeMap
其中HashMap要求key是必須可哈希的類型,BTreeMap的key必須是可排序的。
Value必須是在編譯期已知大小的類型。
示例:
use std::collections::BTreeMap;
use std::collections::HashMap;
let mut hmap = HashMap::new();
let mut bmap = BTreeMap::new();
hmap.insert(1,”a”);
bmap.insert(1,”a”);
集合:HashSet和BTreeSet
HashSet
l??集合中的元素應(yīng)該是唯一的。
l??HashSet中的元素都是可哈希的類型,BTreeSet中的元素必須是可排序的。
l??HashSet應(yīng)該是無序的,BTreeSet應(yīng)該是有序的。
示例:
use std::collections::BTreeSet;
use std::collections::HashSet;
let mut hset = HashSet::new();
let mut bset = BTreeSet::new();
hset.insert(”This is a hset.”);
bset.insert(”This is a bset”);
優(yōu)先隊列:BinaryHeap
Rust提供的優(yōu)先隊列是基于二叉最大堆(Binary Heap)實現(xiàn)的。
示例:
use std::collections::BinaryHeap;
let mut heap = BinaryHeap::new();
heap.peek();??? ??????????????????? ???=> peek是取出堆中最大的元素
heap.push(98);
容量(Capacity)和大?。⊿ize/Len)
無論是Vec還是HashMap,使用這些集合容器類型,最重要的是理解容量(Capacity)和大?。⊿ize/Len)。
容量是指為集合容器分配的內(nèi)存容量。
大小是指集合中包含的元素數(shù)量。
2.2.4 Rust字符串
Rust字符串分為以下幾種類型:
str:表示固定長度的字符串
String:表示可增長的字符串
CStr:表示由C分配而被Rust借用的字符串。這是為了兼容windows系統(tǒng)。
CString:表示由Rust分配且可以傳遞給C函數(shù)使用的C字符串,同樣用于和C語言交互。
OsStr:表示和操作系統(tǒng)相關(guān)的字符串。這是為了兼容windows系統(tǒng)。
OsString:表示OsStr的可變版本。與Rust字符串可以相互交換。
Path:表示路徑,定義于std::path模塊中。Path包裝了OsStr。
PathBuf:跟Path配對,是path的可變版本。PathBuf包裝了OsString。
str屬于動態(tài)大小類型(DST),在編譯期并不能確定其大小。所以在程序中最常見的是str的切片(Slice)類型&str。
&str代表的是不可變的UTF-8字節(jié)序列,創(chuàng)建后無法再為其追加內(nèi)容或更改其內(nèi)容。&str類型的字符串可以存儲在任意地方:
靜態(tài)存儲區(qū)
堆分配
棧分配
具體的見《Rust編程之道》的第249頁。
String類型本質(zhì)是一個成員變量為Vec
String類型由三部分組成:
執(zhí)行堆中字節(jié)序列的指針(as_ptr方法)
記錄堆中字節(jié)序列的字節(jié)長度(len方法)
堆分配的容量(capacity方法)
2.2.4.1?字符串處理方式
Rust中的字符串不能使用索引訪問其中的字符,可以通過bytes和chars兩個方法來分別返回按字節(jié)和按字符迭代的迭代器。
Rust提供了另外兩種方法:get和get_mut來通過指定索引范圍來獲取字符串切片。
具體的見《Rust編程之道》的第251頁。
2.2.4.2?字符串修改
追加字符串:push和push_str,以及extend迭代器
插入字符串:insert和insert_str
連接字符串:String實現(xiàn)了Add<&str>和AddAssign<&str>兩個trait,所以可以使用“+”和“+=”來連接字符串
更新字符串:通過迭代器或者某些unsafe的方法
刪除字符串:remove、pop、truncate、clear和drain
具體的見《Rust編程之道》的第255頁。
2.2.4.3?字符串的查找
Rust總共提供了20個方法涵蓋了以下幾種字符串匹配操作:
存在性判斷
位置匹配
分割字符串
捕獲匹配
刪除匹配
替代匹配
具體的見《Rust編程之道》的第256頁。
2.2.4.4?類型轉(zhuǎn)換
parse:將字符串轉(zhuǎn)換為指定的類型
format!宏:將其他類型轉(zhuǎn)成成字符串
2.2.5?格式化規(guī)則
填充字符串寬度:{:5},5是指寬度為5
截取字符串:{:.5}
對齊字符串:{:>}、{:^}、{:<},分別表示左對齊、位于中間和右對齊
{:*^5}?使用*替代默認(rèn)空格來填充
符號+:表示強制輸出整數(shù)的正負(fù)符號
符號#:用于顯示進制的前綴。比如:十六進制0x
數(shù)字0:用于把默認(rèn)填充的空格替換成數(shù)字0
{:x}?:轉(zhuǎn)換成16進制輸出
{:b}?:轉(zhuǎn)換成二進制輸出
{:.5}:指定小數(shù)點后有效位是5
{:e}:科學(xué)計數(shù)法表示
具體的見《Rust編程之道》的第265頁。
2.2.6?原生字符串聲明語法:r”…”
原生字符串聲明語法(r”…”)可以保留原來字符串中的特殊符號。
具體的見《Rust編程之道》的第270頁。
2.2.7?全局類型
Rust支持兩種全局類型:
普通常量(Constant)
靜態(tài)變量(Static)
區(qū)別:
都是在編譯期求值的,所以不能用于存儲需要動態(tài)分配內(nèi)存的類型
普通常量可以被內(nèi)聯(lián)的,它沒有確定的內(nèi)存地址,不可變
靜態(tài)變量不能被內(nèi)聯(lián),它有精確的內(nèi)存地址,擁有靜態(tài)生命周期
靜態(tài)變量可以通過內(nèi)部包含UnsafeCell等容器實現(xiàn)內(nèi)部可變性
靜態(tài)變量還有其他限制,具體的見《Rust編程之道》的第326頁
普通常量也不能引用靜態(tài)變量
在存儲的數(shù)據(jù)比較大,需要引用地址或具有可變性的情況下使用靜態(tài)變量。否則,應(yīng)該優(yōu)先使用普通常量。
但也有一些情況是這兩種全局類型無法滿足的,比如想要使用全局的HashMap,在這種情況下,推薦使用lazy_static包。利用lazy_static包可以把定義全局靜態(tài)變量延遲到運行時,而非編譯時。
2.3 trait
trait是對類型行為的抽象。trait是Rust實現(xiàn)零成本抽象的基石,它有如下機制:
trait是Rust唯一的接口抽象方式;
可以靜態(tài)分發(fā),也可以動態(tài)分發(fā);
可以當(dāng)做標(biāo)記類型擁有某些特定行為的“標(biāo)簽”來使用。
示例:
struct Duck;
struct Pig;
trait?Fly {
fn fly(&self) -> bool;
}
impl Fly for Duck?{
fn fly(&self) -> bool {
return true;
}
}
impl Fly for Pig?{
fn fly(&self) -> bool {
return false;
}
}
靜態(tài)分發(fā)和動態(tài)分發(fā)的具體介紹可見《Rust編程之道》的第46頁。
trait限定
以下這些需要繼續(xù)深入理解第三章并總結(jié)。待后續(xù)繼續(xù)補充。
trait對象
標(biāo)簽trait
Copy trait
Deref解引用
as操作符
From和Into
2.4?指針
2.3.1?引用Reference
用&和& mut操作符來創(chuàng)建。受Rust的安全檢查規(guī)則的限制。
引用是Rust提供的一種指針語義。引用是基于指針的實現(xiàn),他與指針的區(qū)別是:指針保存的是其指向內(nèi)存的地址,而引用可以看做某塊內(nèi)存的別名(Alias)。
在所有權(quán)系統(tǒng)中,引用&x也可以稱為x的借用(Borrowing)。通過&操作符來完成
所有權(quán)租借
。
2.3.2?原生指針(裸指針)
*const T和*mut T。可以在unsafe塊下任意使用,不受Rust的安全檢查規(guī)則的限制。
2.3.3?智能指針
實際上是一種結(jié)構(gòu)體,只是行為類似指針。智能指針是對指針的一層封裝,提供了一些額外的功能,比如自動釋放堆內(nèi)存。
智能指針區(qū)別于常規(guī)結(jié)構(gòu)體的特性在于:它實現(xiàn)了Deref和Drop這兩個trait。
Deref:提供了解引用能力
Drop:提供了自動析構(gòu)的能力
2.3.3.1?智能指針有哪些
智能指針擁有資源的所有權(quán),而普通引用只是對所有權(quán)的借用。
Rust中的值默認(rèn)被分配到棧內(nèi)存??梢酝ㄟ^Box
String
Vec
String類型和Vec類型的值都是被分配到堆內(nèi)存并返回指針的,通過將返回的指針封裝來實現(xiàn)Deref和Drop。
Box
Box
Arc
RC
單線程引用計數(shù)指針,不是線程安全的類型。
可以將多個所有權(quán)共享給多個變量,每當(dāng)共享一個所有權(quán)時,計數(shù)就會增加一次。具體的見《Rust編程之道》的第149頁。
Weak
是RC
通過clone方法共享的引用所有權(quán)稱為強引用,RC
Weak
Cell
實現(xiàn)字段級內(nèi)部可變的情況。
適合復(fù)制語義類型。
RefCell
適合移動語義類型。
Cell
Cell
具體的見《Rust編程之道》的第151頁。
Cow
Copy on write:一種枚舉體的智能指針。Cow
Cow
Cow
2.3.4?解引用deref
解引用會獲得所有權(quán)。
解引用操作符:?*
哪些實現(xiàn)了deref方法
Box
Cow
Box
2.4?所有權(quán)機制(ownership):
Rust中分配的每塊內(nèi)存都有其所有者,所有者負(fù)責(zé)該內(nèi)存的釋放和讀寫權(quán)限,并且每次每個值只能有唯一的所有者。
在進行賦值操作時,對于可以實現(xiàn)Copy的復(fù)制語義類型,所有權(quán)并未改變。對于復(fù)合類型來說,是復(fù)制還是移動,取決于其成員的類型。
例如:如果數(shù)組的元素都是基本的數(shù)字類型,則該數(shù)組是復(fù)制語義,則會按位復(fù)制。
2.4.1?詞法作用域(生命周期)
match、for、loop、while、if let、while let、花括號、函數(shù)、閉包都會創(chuàng)建新的作用域,相應(yīng)綁定的所有權(quán)會被轉(zhuǎn)移,具體的可見《Rust編程之道》的第129頁。
函數(shù)體本身是獨立的詞法作用域:
當(dāng)復(fù)制語義類型作為函數(shù)參數(shù)時,會按位復(fù)制。
如果是移動語義作為函數(shù)參數(shù),則會轉(zhuǎn)移所有權(quán)。
2.4.2?非詞法作用域聲明周期
借用規(guī)則:?借用方的生命周期不能長于出借方的生命周期。用例見《Rust編程之道》的第157頁。
因為以上的規(guī)則,經(jīng)常導(dǎo)致實際開發(fā)不便,所以引入了非詞法作用域生命周期(Non-Lexical Lifetime,NLL)來改善。
MIR是基于控制流圖(Control Flow Graph,CFG)的抽象數(shù)據(jù)結(jié)構(gòu),它用有向圖(DAG)形式包含了程序執(zhí)行過程中所有可能的流程。所以將基于MIR的借用檢查稱為非詞法作用域的生命周期。
2.4.2?所有權(quán)借用
使用可變借用的前提是:出借所有權(quán)的綁定變量必須是一個可變綁定。
在所有權(quán)系統(tǒng)中,引用&x也可以稱為x的借用(Borrowing)。通過&操作符來完成所有權(quán)租借。所以引用并不會造成綁定變量所有權(quán)的轉(zhuǎn)移。
引用在離開作用域之時,就是其歸還所有權(quán)之時。
不可變借用(引用)不能再次出借為可變借用。
不可變借用可以被出借多次。
可變借用只能出借一次。
不可變借用和可變借用不能同時存在,針對同一個綁定而言。
借用的生命周期不能長于出借方的生命周期。具體的舉例見《Rust編程之道》的第136頁。
核心原則:共享不可變,可變不共享。
因為解引用操作會獲得所有權(quán),所以在需要對移動語義類型(如&String)進行解引用時需要特別注意。
2.4.3?生命周期參數(shù)
編譯器的借用檢查機制無法對跨函數(shù)的借用進行檢查,因為當(dāng)前借用的有效性依賴于詞法作用域。所以,需要開發(fā)者顯式的對借用的生命周期參數(shù)進行標(biāo)注。
2.4.3.1?顯式生命周期參數(shù)
生命周期參數(shù)必須是以單引號開頭;
參數(shù)名通常都是小寫字母,例如:'a;
生命周期參數(shù)位于引用符號&后面,并使用空格來分割生命周期參數(shù)和類型。
標(biāo)注生命周期參數(shù)是由于borrowed pointers導(dǎo)致的。因為有borrowed pointers,當(dāng)函數(shù)返回borrowed pointers時,為了保證內(nèi)存安全,需要關(guān)注被借用的內(nèi)存的生命周期(lifetime)。
標(biāo)注生命周期參數(shù)并不能改變?nèi)魏我玫纳芷陂L短,它只用于編譯器的借用檢查,來防止懸垂指針。即:生命周期參數(shù)的目的是幫助借用檢查器驗證合法的引用,消除懸垂指針。
例如:
&i32;? ==>?引用
&'a i32;??? ==>?標(biāo)注生命周期參數(shù)的引用
&'a mut i32;??? ==>?標(biāo)注生命周期參數(shù)的可變引用
允許使用&'a str;的地方,使用&'static str;也是合法的。
對于'static:當(dāng)borrowed pointers指向static對象時需要聲明'static lifetime。
如:
static STRING: &'static str = "bitstring";
2.4.3.2?函數(shù)簽名中的生命周期參數(shù)
fn foo<'a>(s:?&'a?str, t:?&'a?str) ->?&'a?str;
函數(shù)名后的<'a>為生命周期參數(shù)的聲明。函數(shù)或方法參數(shù)的生命周期叫做輸入生命周期(input lifetime),而返回值的生命周期被稱為輸出生命周期(output lifetime)。
規(guī)則:
禁止在沒有任何輸入?yún)?shù)的情況下返回引用,因為會造成懸垂指針。
從函數(shù)中返回(輸出)一個引用,其生命周期參數(shù)必須與函數(shù)的參數(shù)(輸入)相匹配,否則,標(biāo)注生命周期參數(shù)也毫無意義。
對于多個輸入?yún)?shù)的情況,也可以標(biāo)注不同的生命周期參數(shù)。具體的舉例見《Rust編程之道》的第139頁。
2.4.3.3?結(jié)構(gòu)體定義中的生命周期參數(shù)
結(jié)構(gòu)體在含有引用類型成員的時候也需要標(biāo)注生命周期參數(shù),否則編譯失敗。
例如:
struct Foo<'a>?{
part:?&'a str,
}
這里生命周期參數(shù)標(biāo)記,實際上是和編譯器約定了一個規(guī)則:
結(jié)構(gòu)體實例的生命周期應(yīng)短于或等于任意一個成員的生命周期。
2.4.3.4?方法定義中的生命周期參數(shù)
結(jié)構(gòu)體中包含引用類型成員時,需要標(biāo)注生命周期參數(shù),則在impl關(guān)鍵字之后也需要聲明生命周期參數(shù),并在結(jié)構(gòu)體名稱之后使用。
例如:
impl<'a>?Foo<'a>?{
fn split_first(s:?&'a str) ->?&'a str?{
…
}
}
在添加生命周期參數(shù)'a之后,結(jié)束了輸入引用的生命周期長度要長于結(jié)構(gòu)體Foo實例的生命周期長度。
注:枚舉體和結(jié)構(gòu)體對生命周期參數(shù)的處理方式是一樣的。
2.4.3.5?靜態(tài)生命周期參數(shù)
靜態(tài)生命周期?'static:是Rust內(nèi)置的一種特殊的生命周期。'static生命周期存活于整個程序運行期間。所有的字符串字面量都有生命周期,類型為& 'static str。
字符串字面量是全局靜態(tài)類型,他的數(shù)據(jù)和程序代碼一起存儲在可執(zhí)行文件的數(shù)據(jù)段中,其地址在編譯期是已知的,并且是只讀的,無法更改。
2.4.3.6?省略生命周期參數(shù)
滿足以下三條規(guī)則時,可以省略生命周期參數(shù)。該場景下,是將其硬編碼到Rust編譯器重,以便編譯期可以自動補齊函數(shù)簽名中的生命周期參數(shù)。
生命周期省略規(guī)則:
每一個在輸入位置省略的生命周期都將成為一個不同的生命周期參數(shù)。即對應(yīng)一個唯一的生命周期參數(shù)。
如果只有一個輸入的生命周期位置(無論省略還是沒省略),則該生命周期都將分配給輸出生命周期。
如果有多個輸入生命周期位置,而其中包含著?&self?或者?&mut self,那么?self?的生命周期都將分配給輸出生命周期。
以上這部分規(guī)則還沒理解透徹,需要繼續(xù)熟讀《Rust編程之道》的第143頁。
2.4.3.7?生命周期限定
生命周期參數(shù)可以向trait那樣作為泛型的限定,有以下兩種形式:
T: 'a,表示T類型中的任何引用都要“獲得”和'a一樣長。
T: Trait + 'a,表示T類型必須實現(xiàn)Trait這個trait,并且T類型中任何引用都要“活的”和'a一樣長。
具體的舉例見《Rust編程之道》的第145頁。
2.4.3.8 trait對象的生命周期
具體的舉例見《Rust編程之道》的第146頁。
2.4.3.9?高階生命周期
Rust還提供了高階生命周期(Higher-Ranked Lifetime)方案,該方案也叫高階trait限定(Higher-Ranked Trait Bound,HRTB)。該方案提供了for<>語法。
for<>語法整體表示此生命周期參數(shù)只針對其后面所跟著的“對象”。
具體的可見《Rust編程之道》的第192頁。
2.5?并發(fā)安全與所有權(quán)
2.5.1?標(biāo)簽trait:Send和Sync
如果類型T實現(xiàn)了Send:?就是告訴編譯器該類型的實例可以在線程間安全傳遞所有權(quán)。
如果類型T實現(xiàn)了Sync:就是向編譯器表明該類型的實例在多線程并發(fā)中不可能導(dǎo)致內(nèi)存不安全,所以可以安全的跨線程共享。
2.5.2?哪些類型實現(xiàn)了Send
2.5.3?哪些類型實現(xiàn)了Sync
軟件開發(fā) Rust
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。