摘要:從開發環境、語法、屬性、內存管理和Unicode等五部分,爲你帶來一份詳細的Rust語言學習的精華總結內容。
1、Rust開發環境指南
1.1 Rust代碼執行html
根據編譯原理知識,編譯器不是直接將源語言翻譯爲目標語言,而是翻譯爲一種「中間語言」,編譯器從業人員稱之爲「IR」--指令集,以後再由中間語言,利用後端程序和設備翻譯爲目標平臺的彙編語言。前端
Rust代碼執行:算法
1) Rust代碼通過分詞和解析,生成AST(抽象語法樹)。編程
2) 而後把AST進一步簡化處理爲HIR(High-level IR),目的是讓編譯器更方便的作類型檢查。windows
3) HIR會進一步被編譯爲MIR(Middle IR),這是一種中間表示,主要目的是:後端
a) 縮短編譯時間;數組
b) 縮短執行時間;安全
c) 更精確的類型檢查。數據結構
4) 最終MIR會被翻譯爲LLVM IR,而後被LLVM的處理編譯爲能在各個平臺上運行的目標機器碼。多線程
Ø IR
:中間語言
Ø HIR
:高級中間語言
Ø MIR
:中級中間語言
Ø LLVM
:Low Level Virtual Machine,底層虛擬機。
LLVM是構架編譯器(compiler)的框架系統,以C++編寫而成,用於優化以任意程序語言編寫的程序的編譯時間(compile-time)、連接時間(link-time)、運行時間(run-time)以及空閒時間(idle-time)
無疑,不一樣編譯器的中間語言IR是不同的,而IR能夠說是集中體現了這款編譯器的特徵:他的算法,優化方式,彙編流程等等,想要徹底掌握某種編譯器的工做和運行原理,分析和學習這款編譯器的中間語言無疑是重要手段。
因爲中間語言至關於一款編譯器前端和後端的「橋樑」,若是咱們想進行基於LLVM的後端移植,無疑須要開發出對應目標平臺的編譯器後端,想要順利完成這一工做,透徹瞭解LLVM的中間語言無疑是很是必要的工做。
LLVM相對於gcc的一大改進就是大大提升了中間語言的生成效率和可讀性, LLVM的中間語言是一種介於c語言和彙編語言的格式,他既有高級語言的可讀性,又能比較全面地反映計算機底層數據的運算和傳輸的狀況,精煉而又高效。
1.1.1 MIR
MIR是基於控制流圖(Control Flow Graph,CFG)的抽象數據結構,它用有向圖(DAG)形式包含了程序執行過程當中全部可能的流程。因此將基於MIR的借用檢查稱爲非詞法做用域的生命週期。
MIR由一下關鍵部分組成:
- l
基本塊(Basic block,bb)
,他是控制流圖的基本單位,
Ø 語句(statement)
Ø 終止句(Terminator)
- l
本地變量
,佔中內存的位置,好比函數參數、局部變量等。 - l
位置(Place)
,在內存中標識未知的額表達式。 - l
右值(RValue)
,產生值的表達式。
具體的工做原理見《Rust編程之道》的第158和159頁。
能夠在http://play.runst-lang.org中生成MIR代碼。
1.1 Rust安裝
Ø 方法一:見Rust官方的installation章節介紹。
實際上就是調用該命令來安裝便可:curl https://sh.rustup.rs -sSf | sh
Ø 方法二:下載離線的安裝包來安裝,具體的可見Rust官方的Other Rust Installation Methods章節。
1.2 Rust編譯&運行
1.2.1 Cargo包管理
Cargo
是Rust中的包管理工具,第三方包叫作crate
。
Cargo
一共作了四件事:
- l 使用兩個元數據(
metadata
)文件來記錄各類項目信息 - l 獲取並構建項目的依賴關係
- l 使用正確的參數調用
rustc
或其餘構建工具來構建項目 - l 爲Rust生態系統開發建議了統一標準的工做流
Cargo
文件:
- l
Cargo.lock
:只記錄依賴包的詳細信息,不須要開發者維護,而是由Cargo自動維護 - l
Cargo.toml
:描述項目所須要的各類信息,包括第三方包的依賴
cargo編譯默認爲Debug
模式,在該模式下編譯器不會對代碼進行任何優化。也可使用--release
參數來使用發佈模式。release
模式,編譯器會對代碼進行優化,使得編譯時間變慢,可是代碼運行速度會變快。
官方編譯器rustc
,負責將rust源碼編譯爲可執行的文件或其餘文件(.a、.so、.lib等)。例如:rustc box.rs
Rust還提供了包管理器Cargo
來管理整個工做流程。例如:
l
cargo new
first_pro_create
:建立名爲first_pro_create的項目l
cargo new --lib
first_lib_create
:建立命令first_lib_create的庫項目l
cargo doc
l
cargo doc --open
l
cargo test
l
cargo test -- --test-threads=1
l
cargo build
l
cargo build --release
l
cargo run
l
cargo install --path
l
cargo uninstall
first_pro_create
l
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默認會把連字符
轉換成下劃線
。
Rust也不建議以「-rs
」或「_rs
」爲後綴來命名包名,並且會強制性的將此後綴去掉。
具體的見《Rust編程之道》的第323頁。
1.4 Rust經常使用命令
1.5 Rust命令規範
Ø 函數
: 蛇形命名法(snake_case),例如:func_name()
Ø 文件名
: 蛇形命名法(snake_case),例如file_name.rs、main.rs
Ø 臨時變量名
:蛇形命名法(snake_case)
Ø 全局變量名
:
Ø 結構體
: 大駝峯命名法,例如:struct FirstName { name: String}
Ø enum類型
: 大駝峯命名法。
Ø 關聯常量
:常量名必須所有大寫。什麼是關聯常量見《Rust編程之道》的第221頁。
Ø Cargo默認會把連字符
「-
」轉換成下劃線
「_
」。
Ø Rust也不建議以「-rs
」或「_rs
」爲後綴來命名包名,並且會強制性的將此後綴去掉。
2、Rust語法
2.1 疑問&總結
2.1.1 Copy語義 && Move語義(Move語義必須轉移全部權)
類型愈來愈豐富,值類型和引用類型難以描述所有狀況,因此引入了:
Ø 值語義(Value Semantic)
複製之後,兩個數據對象擁有的存儲空間是獨立的,互不影響。
基本的原生類型
都是值語義
,這些類型也被稱爲POD(Plain old data)。POD類型都是值語義,可是值語義類型並不必定都是POD類型。
具備值語義的原生類型,在其做爲右值進行賦值操做時,編譯器會對其進行按位複製。
Ø 引用語義(Reference Semantic)
複製之後,兩個數據對象互爲別名。操做其中任意一個數據對象,則會影響另一個。
智能指針Box<T>
封裝了原生指針,是典型的引用類型。Box<T>
沒法實現Copy,意味着它被rust標記爲了引用語義
,禁止按位複製。
引用語義類型不能實現Copy,但能夠實現Clone的clone方法,以實現深複製。
在Rust中,能夠經過是否實現Copy trait來區分數據類型的值語義
和引用語義
。但爲了更加精準,Rust也引用了新的語義:複製(Copy)語義
和移動(Move)語義
。
Ø Copy語義
:對應值語義,即實現了Copy的類型在進行按位複製時是安全的。
Ø Move語義
:對應引用語義。在Rust中不容許按位複製,只容許移動全部權。
2.1.2 哪些實現了Copy
Ø 結構體
:當成員都是複製語義類型時,不會自動實現Copy。
Ø 枚舉體
:當成員都是複製語義類型時,不會自動實現Copy。
結構體
&& 枚舉體
:
1) 全部成員都是複製語義類型時,須要添加屬性#[derive(Debug,Copy,Clone)]
來實現Copy。
2) 若是有移動語義類型的成員,則沒法實現Copy。
Ø 元組類型
:自己實現了Copy。若是元素均爲複製語義類型,則默認是按位複製,不然執行移動語義。
Ø 字符串字面量
&str
: 支持按位複製。例如:c = 「hello」;
則c就是字符串字面量。
2.1.3 哪些未實現Copy
Ø 字符串對象String
:to_string()
能夠將字符串字面量轉換爲字符串對象。
2.1.4 哪些實現了Copy trait
Ø 原生整數類型
對於實現Copy的類型,其clone方法只須要簡單的實現按位複製便可。
2.1.5 哪些未實現Copy trait
Ø Box<T>
實現了Copy trait,有什麼做用?
實現Copy trait的類型同時擁有複製語義,在進行賦值或者傳入函數等操做時,默認會進行按位複製。
Ø 對於默承認以安全的在棧上進行按位複製的類型,就只須要按位複製,也方便管理內存。
Ø 對於默認只可在堆上存儲的數據,必須進行深度複製。深度複製須要在堆內存中從新開闢空間,這會帶來更多的性能開銷。
2.1.6 哪些是在棧上的?哪些是在堆上的?
2.1.7 let綁定
Ø Rust聲明的綁定默認爲不可變。
Ø 若是須要修改,能夠用mut
來聲明綁定是可變的。
2.2 數據類型
不少編程語言中的數據類型是分爲兩類:
Ø 值類型
通常是指能夠將數據都保存在同一位置的類型。例如數值、布爾值、結構體等都是值類型。
值類型有:
l
原生類型
l
結構體
l
枚舉體
Ø 引用類型
會存在一個指向實際存儲區的指針。好比一般一些引用類型會將數據存儲在堆中,而棧中只存放指向堆中數據的地址(指針)。
引用類型有:
l
普通引用類型
l
原生指針類型
2.2.1 基本數據類型
布爾類型
bool類型只有兩個值:true
和false
。
基本數字類型
主要關注取值範圍,具體的見《Rust編程之道》的第26頁。
字符類型
用單引號
來定義字符(char)類型。字符類型表明一個Unicode
標量值,每一個字節佔4個字節。
數組類型
數組的類型簽名爲[T; N]
。T
是一個泛型標記,表明數組中元素的某個具體類型。N
表明數組長度,在編譯時必須肯定其值。
數組特色:
- l 大小固定
- l 元素均爲同類型
- l 默認不可變
切片類型
切片(Slice)類型是對一個數組的引用片斷。在底層,切片表明一個指向數組起始位置的指針和數組長度。用[T]
類型表示連續序列,那麼切片類型就是&[T]
和&mut[T]
。
具體的見《Rust編程之道》的第30頁。
str字符串類型
字符串類型str
,一般是以不可變借用的形式存在,即&str
(字符串切片)。
Rust將字符串分爲兩種:
1) &str
:固定長度字符串
2) String
:能夠隨意改變其長度。
&str
字符串類型由兩部分組成:
1) 指向字符串序列的指針;
2) 記錄長度的值。
&str
存儲於棧上,str字符串序列存儲於程序的靜態只讀數據段或者堆內存中。
&str
是一種胖指針
。
never類型
never
類型,即!
。該類型用於表示永遠不可能有返回值的計算類型。
其餘(此部分不屬於基本數據類型)
此部分不屬於基本數據類型,因爲編排問題,暫時先放在此處。
胖指針
胖指針
:包含了動態大小類型地址信息和攜帶了長度信息的指針。
具體的見《Rust編程之道》的第54頁。
零大小類型
零大小類型(Zero sized Type,ZST)的特色是:它們的值就是其自己,運行時並不佔用內存空間。
單元類型
和單元結構體
大小爲零,由單元類型組成的數組大小也是零。
ZST類型表明的意義是「空
」。
底類型
底類型
實際上是介紹過的never
類型,用歎號
(!
)表示。它的特色是:
- l 沒有值
- l 是其餘任意類型的子類型
若是說ZST類型表示「空
」的話,那麼底類型就表示「無
」。
底類型
無值,並且它能夠等價於任意類型。
具體的見《Rust編程之道》的第57頁。
2.2.2 複合數據類型
元組
Rust提供了4中複合數據類型:
l
元組(Tuple)
l
結構體(Struct)
l
枚舉體(Enum)
l
聯合體(Union)
先來介紹元組。元組
是一種異構有限序列,形如(T,U,M,N)
。所謂異構,就是指元組內的元素能夠是不一樣類型。所謂有限,是指元組有固定的長度。
- l 空元組:
()
- l 只有一個值時,須要加逗號:
(0,)
結構體
Rust提供了3中結構體:
l
具名結構體
l
元組結構體
l
單元結構體
例如:
Ø 具名結構體:
struct People { name: &’static str, }
Ø 元組結構體:字段沒有名稱,只有類型:
struct Color(i32, i32, i32);
當一個元組結構體只有一個字段的時候,稱爲New Type
模式。例如:
struct Integer(u32);
Ø 單元結構體:沒有任何字段的結構體。單元結構體實例就是其自己。
struct Empty;
結構體更新語法
使用Struct更新語法(..
)從其餘實例建立新實例。當新實例使用舊實例的大部分值時,可使用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 若是結構體使用了移動語義的成員字段,則不容許實現Copy。
- l Rust不容許包含了String類型字段的結構體實現Copy。
- l 更新語法會轉移字段的全部權。
枚舉體
該類型包含了所有可能的狀況,能夠有效的防止用戶提供無效值。例如:
enum Number { Zero, One, }
Rust還支持攜帶類型參數的枚舉體。這樣的枚舉值本質上屬於函數類型,他能夠經過顯式的指定類型來轉換爲函數指針類型。例如:
enum IpAddr { V4(u8, u8, u8, u8), V6(String), }
枚舉體在Rust中屬於很是重要的類型之一。例如:Option枚舉
類型。
聯合體
2.2.3 經常使用集合類型
線性序列:向量
在Rust標準庫std::collections模塊下有4中通用集合類型,分別以下:
- 線性序列:
向量(Vec)
、雙端隊列(VecDeque)
、鏈表(LinkedList)
- Key-Value映射表:
無序哈希表(HashMap)
、有序映射表(BTreeMap)
- 集合類型:
無序集合(HashSet)
、有序集合(BTreeSet)
- 優先隊列:
二叉堆(BinaryHeap)
具體的見《Rust編程之道》的第38頁和271頁。
向量也是一種數組,和基本數據類型中的數組的區別在於:向量可動態增加。
示例:
let mut v1 = vec![]; let mut v2 = vec![0; 10]; let mut v3 = Vec::new();
vec!
是一個宏,用來建立向量字面量。
線性序列:雙端隊列
雙端隊列(Double-ended Queue,縮寫Deque)是一種同時具備隊列(先進先出)和棧(後進先出)性質的數據結構。
雙端隊列中的元素能夠從兩端彈出,插入和刪除操做被限定在隊列的兩端進行。
示例:
use std::collections::VecDeque; let mut buf = VecDeque::new(); buf.push_front(1); buf.get(0); buf.push_back(2);
線性序列:鏈表
Rust提供的鏈表是雙向鏈表,容許在任意一端插入或彈出元素。最好使用Vec或VecDeque類型,他們比鏈表更加快速,內存訪問效率更高。
示例:
use std::collections::LinkedList; let mut list = LinkedList::new(); list.push_front(‘a’); list.append(&mut list2); list.push_back(‘b’);
Key-Value映射表:HashMap和BTreeMap
HashMap<K, V>
=> 無序BTreeMap<K, V>
=> 有序
其中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<K>
和BTreeSet<K>
其實就是HashMap<K, V>
和BTreeMap<K, V>
把Value設置爲空元組的特定類型。
- l 集合中的元素應該是惟一的。
- l
HashSet
中的元素都是可哈希的類型,BTreeSet
中的元素必須是可排序的。 - l
HashSet
應該是無序的,BTreeSet
應該是有序的。
示例:
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」);
優先隊列:BinaryHeap
Rust提供的優先隊列是基於二叉最大堆(Binary Heap)
實現的。
示例:
use std::collections::BinaryHeap; let mut heap = BinaryHeap::new(); heap.peek(); => peek是取出堆中最大的元素 heap.push(98);
容量(Capacity)和大小(Size/Len)
不管是Vec仍是HashMap,使用這些集合容器類型,最重要的是理解容量(Capacity
)和大小(Size/Len)
。
容量
是指爲集合容器分配的內存容量。
大小
是指集合中包含的元素數量。
2.2.4 Rust字符串
Rust字符串分爲如下幾種類型:
- l
str
:表示固定長度的字符串 - l
String
:表示可增加的字符串 - l
CStr
:表示由C分配而被Rust借用的字符串。這是爲了兼容windows系統。 - l
CString
:表示由Rust分配且能夠傳遞給C函數使用的C字符串,一樣用於和C語言交互。 - l
OsStr
:表示和操做系統相關的字符串。這是爲了兼容windows系統。 - l
OsString
:表示OsStr的可變版本。與Rust字符串能夠相互交換。 - l
Path
:表示路徑,定義於std::path模塊中。Path包裝了OsStr。 - l
PathBuf
:跟Path配對,是path的可變版本。PathBuf包裝了OsString。
str
屬於動態大小類型(DST),
在編譯期並不能肯定其大小。因此在程序中最多見的是str
的切片(Slice)類型&str。
&str
表明的是不可變的UTF-8
字節序列,建立後沒法再爲其追加內容或更改其內容。&str
類型的字符串能夠存儲在任意地方:
Ø
靜態存儲區
Ø
堆分配
Ø
棧分配
具體的見《Rust編程之道》的第249頁。
String
類型本質是一個成員變量爲Vec<u8>
類型的結構體,因此它是直接將字符內容存放於堆中的。
String
類型由三部分組成:
Ø 執行堆中字節序列的指針(as_ptr方法)
Ø 記錄堆中字節序列的字節長度(len方法)
Ø 堆分配的容量(capacity方法)
2.2.4.1 字符串處理方式
Rust中的字符串不能使用索引訪問其中的字符,能夠經過bytes
和chars
兩個方法來分別返回按字節
和按字符
迭代的迭代器。
Rust提供了另外兩種方法:get
和get_mut
來經過指定索引範圍來獲取字符串切片。
具體的見《Rust編程之道》的第251頁。
2.2.4.2 字符串修改
Ø 追加字符串:push
和push_str
,以及extend
迭代器
Ø 插入字符串:insert
和insert_str
Ø 鏈接字符串:String實現了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 類型轉換
Ø parse
:將字符串轉換爲指定的類型
Ø format!宏
:將其餘類型轉成成字符串
2.2.5 格式化規則
- l 填充字符串寬度:
{:5}
,5是指寬度爲5 l
截取字符串:{:.5}
l
對齊字符串:{:>}
、{:^}
、{:<}
,分別表示左對齊
、位於中間
和右對齊
l
{:*^5}
使用*
替代默認空格
來填充- l 符號
+
:表示強制輸出整數的正負符號 - l 符號
#
:用於顯示進制的前綴。好比:十六進制0x - l 數字
0
:用於把默認填充的空格替換成數字0 - l
{:x}
:轉換成16進制輸出 - l
{:b}
:轉換成二進制輸出 l
{:.5}
:指定小數點後有效位是5- l
{:e}
:科學計數法表示
具體的見《Rust編程之道》的第265頁。
2.2.6 原生字符串聲明語法:r」…」
原生字符串聲明語法(r」…」
)能夠保留原來字符串中的特殊符號。
具體的見《Rust編程之道》的第270頁。
2.2.7 全局類型
Rust支持兩種全局類型:
- l
普一般量(Constant)
- l
靜態變量(Static)
區別:
- l 都是在
編譯期
求值的,因此不能用於存儲須要動態分配內存的類型 - l 普一般量
能夠被內聯
的,它沒有肯定的內存地址,不可變 - l 靜態變量
不能被內聯
,它有精確的內存地址,擁有靜態生命週期 - l 靜態變量能夠經過內部包含UnsafeCell等容器
實現內部可變性
- l 靜態變量還有其餘限制,具體的見《Rust編程之道》的第326頁
- l 普一般量也不能引用靜態變量
在存儲的數據比較大
,須要引用地址
或具備可變性
的狀況下使用靜態變量。不然,應該優先使用普一般量。
但也有一些狀況是這兩種全局類型沒法知足的,好比想要使用全局的HashMap,在這種狀況下,推薦使用lazy_static包。利用lazy_static包能夠把定義全局靜態變量延遲到運行
時,而非編譯
時。
2.3 trait
trait是對類型行爲的抽象。trait是Rust實現零成本抽象的基石,它有以下機制:
- l trait是Rust惟一的接口抽象方式;
- l 能夠靜態分發,也能夠動態分發;
- l 能夠當作標記類型擁有某些特定行爲的「標籤」來使用。
示例:
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; } }
靜態分發和動態分發的具體介紹可見《Rust編程之道》的第46頁。
trait限定
如下這些須要繼續深刻理解第三章並總結。待後續繼續補充。
trait對象
標籤trait
Copy trait
Deref解引用
as操做符
From和Into
2.4 指針
2.3.1 引用Reference
用&
和& mut
操做符來建立。受Rust的安全檢查規則的限制。
引用是Rust提供的一種指針語義。引用是基於指針的實現,他與指針的區別是:指針保存的是其指向內存的地址,而引用能夠看作某塊內存的別名(Alias)。
在全部權系統中,引用&x
也能夠稱爲x的借用(Borrowing)。經過&
操做符來完成全部權租借。
2.3.2 原生指針(裸指針)
*const T
和*mut T
。能夠在unsafe
塊下任意使用,不受Rust的安全檢查規則的限制。
2.3.3 智能指針
其實是一種結構體,只是行爲相似指針。智能指針是對指針的一層封裝,提供了一些額外的功能,好比自動釋放堆內存。
智能指針區別於常規結構體的特性在於:它實現了Deref
和Drop
這兩個trait。
Ø Deref
:提供瞭解引用能力
Ø Drop
:提供了自動析構的能力
2.3.3.1 智能指針有哪些
智能指針擁有資源的全部權,而普通引用只是對全部權的借用。
Rust中的值默認被分配到棧內存。能夠經過Box<T>將值裝箱(在堆內存中分配)。
Ø String
Ø
Vec
String
類型和Vec
類型的值都是被分配到堆內存
並返回指針
的,經過將返回的指針封裝來實現Deref
和Drop
。
Ø
Box<T>
Box<T>是指向類型爲T的堆內存分配值的智能指針。當Box<T>超出做用域範圍時,將調用其析構函數,銷燬內部對象,並自動釋放堆中的內存。
Ø
Arc<T>
Ø
RC<T>
單線程引用計數指針,不是線程安全的類型。
能夠將多個全部權共享給多個變量,每當共享一個全部權時,計數就會增長一次。具體的見《Rust編程之道》的第149頁。
Ø
Weak<T>
是RC<T>的另外一個版本。
經過clone方法共享的引用全部權稱爲強引用,RC<T>是強引用。
Weak<T>共享的指針沒有全部權,屬於弱引用。
具體的見《Rust編程之道》的第150頁。
Ø
Cell<T>
實現字段級內部可變的狀況。
適合複製語義類型。
Ø
RefCell<T>
適合移動語義類型。
Cell<T>和RefCell<T>本質上不屬於智能指針,只是提供內不可變性的容器。
Cell<T>和RefCell<T>使用最多的場景就是配合只讀引用來使用。
具體的見
《Rust編程之道》的第151頁。
Ø Cow<T>
Copy on write:一種枚舉體的智能指針。Cow<T>表示的是全部權的「借用」和「擁有」。Cow<T>的功能是:以不可變的方式訪問借用內容,以及在須要可變借用或全部權的時候再克隆一份數據。
Cow<T>旨在減小複製操做,提升性能,通常用於讀多寫少的場景。
Cow<T>的另外一個用處是統一實現規範。
2.3.4 解引用deref
解引用會得到全部權。
解引用操做符: *
哪些實現了deref方法
Ø Box<T>
:源碼見《Rust編程之道》的第147頁。
Ø Cow<T>
:意味着能夠直接調用其包含數據的不可變方法。具體的要點可見《Rust編程之道》的第155頁。
Ø
Box<T >支持解引用移動, Rc<T>和Arc<T>智能指針不支持解引用移動。
2.4 全部權機制(ownership):
Rust中分配的每塊內存都有其全部者,全部者負責該內存的釋放和讀寫權限,而且每次每一個值只能有惟一的全部者。
在進行賦值操做時,對於能夠實現Copy的複製語義類型,全部權並未改變。對於複合類型來講,是複製仍是移動,取決於其成員的類型。
例如:若是數組的元素都是基本的數字類型,則該數組是複製語義,則會按位複製。
2.4.1 詞法做用域(生命週期)
match
、for
、loop
、while
、if let
、while let
、花括號
、函數
、閉包
都會建立新的做用域,相應綁定的全部權會被轉移,具體的可見《Rust編程之道》的第129頁。
函數體自己是獨立的詞法做用域:
Ø 當複製語義類型做爲函數參數時,會按位複製。
Ø 若是是移動語義做爲函數參數,則會轉移全部權。
2.4.2 非詞法做用域聲明週期
借用規則: 借用方
的生命週期不能長於出借方
的生命週期。用例見《Rust編程之道》的第157頁。
由於以上的規則,常常致使實際開發不便,因此引入了非詞法做用域生命週期(Non-Lexical Lifetime,NLL)
來改善。
MIR是基於控制流圖(Control Flow Graph,CFG)的抽象數據結構,它用有向圖(DAG)形式包含了程序執行過程當中全部可能的流程。因此將基於MIR的借用檢查稱爲非詞法做用域的生命週期。
2.4.2 全部權借用
使用可變借用的前提是:出借全部權的綁定變量必須是一個可變綁定。
在全部權系統中,引用&x
也能夠稱爲x的借用(Borrowing)。經過&
操做符來完成全部權租借
。因此引用並不會形成綁定變量全部權的轉移。
引用在離開做用域之時,就是其歸還全部權之時。
Ø 不可變借用(引用)不能再次出借爲可變借用。
Ø 不可變借用能夠被出借屢次。
Ø 可變借用只能出借一次。
Ø 不可變借用和可變借用不能同時存在,針對同一個綁定而言。
Ø 借用的生命週期不能長於出借方的生命週期。具體的舉例見《Rust編程之道》的第136頁。
核心原則:共享不可變,可變不共享。
由於解引用操做會得到全部權,因此在須要對移動語義類型(如&String)進行解引用時須要特別注意。
2.4.3 生命週期參數
編譯器的借用檢查機制沒法對跨函數的借用進行檢查,由於當前借用的有效性依賴於詞法做用域。因此,須要開發者顯式的對借用的生命週期參數進行標註。
2.4.3.1 顯式生命週期參數
Ø 生命週期參數必須是以單引號
開頭;
Ø 參數名一般都是小寫字母
,例如:'a
;
Ø 生命週期參數位於引用符號&後面
,並使用空格
來分割生命週期參數和類型。
標註生命週期參數是因爲borrowed pointers致使的。由於有borrowed pointers,當函數返回borrowed pointers時,爲了保證內存安全,須要關注被借用的內存的生命週期(lifetime)。
標註生命週期參數並不能改變任何引用的生命週期長短,它只用於編譯器的借用檢查,來防止懸垂指針。即:生命週期參數的目的是幫助借用檢查器驗證合法的引用,消除懸垂指針。
例如:
&i32; ==> 引用 &'a i32; ==> 標註生命週期參數的引用 &'a mut i32; ==> 標註生命週期參數的可變引用 容許使用&'a str;的地方,使用&'static str;也是合法的。 對於'static:當borrowed pointers指向static對象時須要聲明'static lifetime。 如: static STRING: &'static str = "bitstring";
2.4.3.2 函數簽名中的生命週期參數
fn foo<'a>
(s: &'a
str, t: &'a
str) -> &'a
str;
函數名後的<'a>
爲生命週期參數的聲明。函數或方法參數的生命週期叫作輸入生命週期(input lifetime),而返回值的生命週期被稱爲輸出生命週期(output lifetime)。
規則:
Ø 禁止在沒有任何輸入參數的狀況下返回引用,由於會形成懸垂指針。
Ø 從函數中返回(輸出)一個引用,其生命週期參數必須與函數的參數(輸入)相匹配,不然,標註生命週期參數也毫無心義。
對於多個輸入參數的狀況,也能夠標註不一樣的生命週期參數。具體的舉例見《Rust編程之道》的第139頁。
2.4.3.3 結構體定義中的生命週期參數
結構體在含有引用類型成員的時候也須要標註生命週期參數,不然編譯失敗。
例如:
struct Foo<'a> { part: &'a str, }
這裏生命週期參數標記,其實是和編譯器約定了一個規則:
結構體實例的生命週期應短於或等於任意一個成員的生命週期。
2.4.3.4 方法定義中的生命週期參數
結構體中包含引用類型成員時,須要標註生命週期參數,則在impl關鍵字以後也須要聲明生命週期參數,並在結構體名稱以後使用。
例如:
impl<'a> Foo<'a> { fn split_first(s: &'a str) -> &'a str { … } }
在添加生命週期參數'a
以後,結束了輸入引用的生命週期長度要長於結構體Foo實例的生命週期長度。
注:枚舉體和結構體對生命週期參數的處理方式是同樣的。
2.4.3.5 靜態生命週期參數
靜態生命週期
'static
:是Rust內置的一種特殊的生命週期。'static
生命週期存活於整個程序運行期間。全部的字符串字面量都有生命週期,類型爲& 'static str
。
字符串字面量是全局靜態類型,他的數據和程序代碼一塊兒存儲在可執行文件的數據段中,其地址在編譯期是已知的,而且是隻讀的,沒法更改。
2.4.3.6 省略生命週期參數
知足如下三條規則時,能夠省略生命週期參數。該場景下,是將其硬編碼到Rust編譯器重,以便編譯期能夠自動補齊函數簽名中的生命週期參數。
生命週期省略規則:
- l 每個在輸入位置省略的生命週期都將成爲一個不一樣的生命週期參數。即對應一個惟一的生命週期參數。
- l 若是隻有一個輸入的生命週期位置(不管省略仍是沒省略),則該生命週期都將分配給輸出生命週期。
- l 若是有多個輸入生命週期位置,而其中包含着 &self 或者 &mut self,那麼 self 的生命週期都將分配給輸出生命週期。
以上這部分規則還沒理解透徹,須要繼續熟讀《Rust編程之道》的第143頁。
2.4.3.7 生命週期限定
生命週期參數能夠向trait那樣做爲泛型的限定,有如下兩種形式:
- l
T: 'a
,表示T類型中的任何引用都要「得到」和'a
同樣長。 - l
T: Trait + 'a
,表示T類型必須實現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<>
語法總體表示今生命週期參數只針對其後面所跟着的「對象」。
具體的可見《Rust編程之道》的第192頁。
2.5 併發安全與全部權
2.5.1 標籤trait:Send和Sync
Ø 若是類型T實現了Send
: 就是告訴編譯器該類型的實例能夠在線程間安全傳遞全部權。
Ø 若是類型T實現了Sync
:就是向編譯器代表該類型的實例在多線程併發中不可能致使內存不安全,因此能夠安全的跨線程共享。
2.5.2 哪些類型實現了Send
2.5.3 哪些類型實現了Sync
2.6 原生類型
Rust內置的原生類型 (primitive types) 有如下幾類:
- l
布爾類型
:有兩個值true和false。 - l
字符類型
:表示單個Unicode字符,存儲爲4個字節。 - l
數值類型
:分爲有符號整數 (i8, i16, i32, i64, isize)、 無符號整數 (u8, u16, u32, u64, usize) 以及浮點數 (f32, f64)。 - l
字符串類型
:最底層的是不定長類型str,更經常使用的是字符串切片&str和堆分配字符串String, 其中字符串切片是靜態分配的,有固定的大小,而且不可變,而堆分配字符串是可變的。 - l
數組
:具備固定大小,而且元素都是同種類型,可表示爲[T; N]。 - l
切片
:引用一個數組的部分數據而且不須要拷貝,可表示爲&[T]。 - l
元組
:具備固定大小的有序列表,每一個元素都有本身的類型,經過解構或者索引來得到每一個元素的值。 - l
指針
:最底層的是裸指針const T和mut T,但解引用它們是不安全的,必須放到unsafe塊裏。 - l
函數
:具備函數類型的變量實質上是一個函數指針。 - l
元類型
:即(),其惟一的值也是()。
2.7 函數
2.7.1 函數參數
- l 當函數參數按值傳遞時,會轉移全部權或者執行復制(Copy)語義。
- l 當函數參數按引用傳遞時,全部權不會發生變化,可是須要有生命週期參數(符合規則時不須要顯示的標明)。
2.7.2 函數參數模式匹配
- l
ref
:使用模式匹配來獲取參數的不可變引用。 - l
ref mut
:使用模式匹配來獲取參數的可變引用。 - l 除了ref和ref mut,函數參數也可使用通配符來忽略參數。
具體可見《Rust編程之道》的第165頁。
2.7.3 泛型函數
函數參數並未指定具體的類型,而是用了泛型T
,對T
只有一個Mult trait限定,即只有實現了Mul的類型才能夠做爲參數,從而保證了類型安全。
泛型函數並未指定具體類型,而是靠編譯器來進行自動推斷的。若是使用的都是基本原生類型,編譯器推斷起來比較簡單。若是編譯器沒法自動推斷,就須要顯式的指定函數調用的類型。
2.7.4 方法和函數
方法表明某個實例對象的行爲,函數只是一段簡單的代碼,它能夠經過名字來進行調用。方法也是經過名字來進行調用,但它必須關聯一個方法接受者。
2.7.5 高階函數
高階函數是指以函數做爲參數或返回值的函數,它是函數式編程語言最基礎的特性。
具體可見《Rust編程之道》的第168頁。
2.8 閉包Closure
閉包
一般是指詞法閉包,是一個持有外部環境變量的函數。
外部環境
是指閉包定義時所在的詞法做用域。
外部環境變量
,在函數式編程範式中也被稱爲自由變量
,是指並非在閉包內定義的變量。
將自由變量和自身綁定的函數就是閉包
。
閉包的大小在編譯期是未知的。
2.8.1 閉包的基本語法
閉包
由管道符
(兩個對稱的豎線)和花括號
(或圓括號)組成。
Ø 管道符
裏是閉包函數的參數,能夠向普通函數參數那樣在冒號後添加類型標註,也能夠省略。例如:let add = |a, b| -> i32 { a + b };
Ø 花括號
裏包含的是閉包函數執行體,花括號和返回值也能夠省略。
例如:let add = |a, b| a + b;
Ø 當閉包函數沒有參數只有捕獲的自由變量時,管道符裏的參數也能夠省略。
例如: let add = || a + b;
2.8.2 閉包的實現
閉包
是一種語法糖
。閉包不屬於Rust語言提供的基本語法要素,而是在基本語法功能之上又提供的一層方便開發者編程的語法。
閉包和普通函數的差異
就是閉包能夠捕獲環境中的自由變量。
閉包能夠做爲函數參數,這一點直接提高了Rust語言的抽象表達能力。當它做爲函數參數傳遞時,能夠被用做泛型的trait限定,也能夠直接做爲trait對象來使用。
閉包沒法直接做爲函數的返回值,若是要把閉包做爲返回值,必須使用trait對象。
2.8.3 閉包與全部權
閉包表達式會由編譯器自動翻譯爲結構體實例,併爲其實現Fn、FnMut、FnOnce三個trait中的一個。
l
FnOnce
:會轉移方法接收者的全部權。沒有改變環境的能力,只能調用一次。- l
FnMut
:會對方法接收者進行可變借用。有改變環境的能力,能夠屢次調用。 - l
Fn
:會對方法接收者進行不可變借用。沒有改變環境的能力,能夠屢次調用。
Ø 若是要實現Fn
,就必須實現FnMut
和FnOnce
;
Ø 若是要實現FnMut
,就必須實現FnOnce
;
Ø 若是要實現FnOnce
,就不須要實現FnMut
和Fn
。
2.8.3.1 捕獲環境變量的方式
- l 對於
複製語義
類型,以不可變引用(&T)
來進行捕獲。 - l 對於
移動語義
類型,執行移動語義,轉移全部權
來進行捕獲。 - l 對於
可變綁定
,而且在閉包中包含對其進行修改的操做,則以可變引用(&mut T)
來進行捕獲。
具體可見《Rust編程之道》的第178頁。
Rust使用move
關鍵字來強制讓閉包所定義環境中的自由變量轉移到閉包中。
2.8.3.2 規則總結
- l 若是閉包中沒有捕獲任何環境變量,則默認自動實現
Fn
。 - l 若是閉包中捕獲了複製語義類型的環境變量,則:
Ø 若是不須要修改環境變量,不管是否使用move
關鍵字,均會自動實現Fn
。
Ø 若是須要修改環境變量,則自動實現FnMut
。
- l 若是閉包中捕獲了移動語義類型的環境變量,則:
Ø 若是不須要修改環境變量,並且沒有使用move
關鍵字,則會自動實現FnOnce
。
Ø 若是不須要修改環境變量,並且使用move
關鍵字,則會自動實現Fn
。
Ø 若是須要修改環境變量,則自動實現FnMut
。
- l
FnMut
的閉包在使用move
關鍵字時,若是捕獲變量是複製語義類型的,則閉包會自動實現Copy/Clone。若是捕獲變量是移動語義類型的,則閉包不會自動實現Copy/Clone。
2.9 迭代器
Rust使用的是外部迭代器,也就是for循環。外部迭代器:外部能夠控制整個遍歷進程。
Rust中使用了trait來抽象迭代器模式。Iterator trait
是Rust中對迭代器模式的抽象接口。
迭代器主要包含:
- l
next方法
:迭代其內部元素 - l
關聯類型Item
- l
size_hint方法
:返回類型是一個元組,該元組表示迭代器剩餘長度的邊界信息。
示例:
let iterator = iter.into_iter(); let size_lin = iterator.size_hint(); let mut counter = Counter { count: 0}; counter.next();
Iter
類型迭代器,next方法返回的是Option<&[T]>
或Option<&mut [T]>
類型的值。for循環會自動調用迭代器的next
方法。for循環中的循環變量則是經過模式匹配,從next返回的Option<&[T]>
或Option<&mut [T]>
類型中獲取&[T]
或&mut [T]
類型的值。
Iter
類型迭代器在for循環中產生的循環變量爲引用。
IntoIter
類型的迭代器的next方法返回的是Option<T>
類型,在for循環中產生的循環變量是值,而不是引用。
示例:
let v = vec![1, 2, 3];for i in v { …} |
let v = vec![1, 2, 3];for i in v { …} |
let v = vec![1, 2, 3];for i in v { …} |
let v = vec![1, 2, 3];for i in v { …} |
爲了確保size_hint方法能夠得到迭代器長度的準確信息,Rust引入了兩個trait,他們是Iterator的子trait,均被定義在std::iter模塊中。
- l
ExactSizeIterator
:提供了兩個額外的方法len
和is_empty
。 - l
TrustedLen
:像一個標籤trait,只要實現了TrustLen的迭代器,其size_hint獲取的長度信息均是可信的。徹底避免了容器的容量檢查,提高了性能。
2.9.1 IntoIterator trait
若是想要迭代某個集合容器中的元素,必須將其轉換爲迭代器纔可使用。
Rust提供了FromIterator和IntoIterator兩個trait,他們互爲反操做。
- l
FromIterator
:能夠從迭代器轉換爲指定類型。 - l
IntoIterator
:能夠從指定類型轉換爲迭代器。
Intoiter
可使用into_iter
之類的方法來獲取一個迭代器。into_iter的參數時self,表明該方法會轉移方法接收者的全部權。而還有其餘兩個迭代器不用轉移全部權。具體的以下所示:
- l
Intoiter
:轉移全部權,對應self - l
Iter
:獲取不可變借用,對應&self - l
IterMut
:得到可變借用,對應&mut slef
2.9.2 哪些實現了Iterator的類型?
只有實現了Iterator
的類型才能做爲迭代器。
實現了IntoIterator
的集合容器能夠經過into_iter
方法來轉換爲迭代器。
實現了IntoIterator
的集合容器有:
l
Vec<T>
l
&’a [T]
l
&’a mut [T]
=> 沒有爲[T]類型
實現IntoIterator- l
2.9.3 迭代器適配器
經過適配器模式能夠將一個接口轉換成所須要的另外一個接口。適配器模式可以使得接口不兼容的類型在一塊兒工做。
適配器也叫包裝器(Wrapper)。
迭代器適配器,都定義在std::iter
模塊中:
- l
Map
:經過對原始迭代器中的每一個元素調用指定閉包來產生一個新的迭代器。 - l
Chain
:經過鏈接兩個迭代器來建立一個新的迭代器。 - l
Cloned
:經過拷貝原始迭代器中所有元素來建立新的迭代器。 - l
Cycle
:建立一個永遠循環迭代的迭代器,當迭代完畢後,再返回第一個元素開始迭代。 - l
Enumerate
:建立一個包含計數的迭代器,它返回一個元組(i,val),其中i是usize類型,爲迭代的當前索引,val是迭代器返回的值。 - l
Filter
:建立一個機遇謂詞判斷式過濾元素的迭代器。 - l
FlatMap
:建立一個相似Map的結構的迭代器,可是其中不會包含任何嵌套。 - l
FilterMap
:至關於Filter和Map兩個迭代器一次使用後的效果。 - l
Fuse
:建立一個能夠快速遍歷的迭代器。在遍歷迭代器時,只要返回過一次None,那麼以後全部的遍歷結果都爲None。該迭代器適配器能夠用於優化。 - l
Rev
:建立一個能夠反向遍歷的迭代器。
具體可見《Rust編程之道》的第202頁。
Rust能夠自定義迭代器適配器,具體的見《Rust編程之道》的第211頁。
2.10 消費器
迭代器不會自動發生遍歷行爲,須要調用next方法去消費其中的數據。最直接消費迭代器數據的方法就是使用for循環。
Rust提供了for循環以外的用於消費迭代器內數據的方法,叫作消費器(Consumer)。
Rust標準庫std::iter::Iterator
中經常使用的消費器:
- l
any
:能夠查找容器中是否存在知足條件的元素。 - l
fold
:該方法接收兩個參數,第一個爲初始值,第二個爲帶有兩個參數的閉包。其中閉包的第一個參數被稱爲累加器,它會將閉包每次迭代執行的結果進行累計,並最終做爲fold方法的返回值。 - l
collect
:專門用來將迭代器轉換爲指定的集合類型。 l
all
l
for_each
l
position
2.11 鎖
- l
RwLock讀寫鎖
:是多讀單寫鎖,也叫共享獨佔鎖。它容許多個線程讀,單個線程寫。可是在寫的時候,只能有一個線程佔有寫鎖;而在讀的時候,容許任意線程獲取讀鎖。讀鎖和寫鎖不能被同時獲取。 - l
Mutex互斥鎖
:只容許單個線程讀和寫。
3、 Rust屬性
Ø #[lang = 「drop」]
: 將drop標記爲語言項
Ø #[derive(Debug)]
:
Ø #[derive(Copy, Clone)]
:
Ø #[derive(Debug,Copy,Clone)]
:
Ø #[lang = 「owned_box」]
: Box<T>與原生類型不一樣,並不具有類型名稱,它表明全部權惟一的智能指針的特殊性,須要使用lang item來專門識別。
Ø #[lang = 「fn/fn_mut/fn_once」]
:表示其屬於語言項,分別以fn、fn_mut、fn_once名稱來查找這三個trait。
l
fn_once
:會轉移方法接收者的全部權
l fn_mut
:會對方法接收者進行可變借用
l fn
:會對方法接收者進行不可變借用
Ø #[lang = 「rust_pareen_sugar」]
:表示對括號調用語法的特殊處理。
Ø #[must_use=」iterator adaptors are lazy ……」]
:用來發出警告,提示開發者迭代器適配器是惰性的。
4、內存管理
4.1 內存回收
drop-flag:在函數調用棧中爲離開做用域的變量自動插入布爾標記,標註是否調用析構函數,這樣,在運行時就能夠根據編譯期作的標記來調用析構函數。
實現了Copy的類型,是沒有析構函數的。由於實現了Copy的類型會複製,其生命週期不受析構函數的影響。
須要繼續深刻理解第4章並總結,待後續補充。
5、unicode
Unicode字符集至關於一張表,每一個字符對應一個非負整數,該數字稱爲碼點(Code Point)
。
這些碼點也分爲不一樣的類型:
- l 標量值
- l 代理對碼點
- l 非字符碼點
- l 保留碼點
- l 私有碼點
標量值
是指實際存在對應字符的碼位,其範圍是0x0000~0xD7FF
和0xE000~0x10FFFF
兩段。
Unicode字符集的每一個字符佔4
個字節,使用的存儲方式是:碼元(Code Unit)
組成的序列。
碼元
是指用於處理和交換編碼文本的最小比特組合。
Unicode字符編碼表:
- l
UTF-8
=>1
字節碼元 - l
UTF-16
=>2
字節碼元 - l
UTF-32
=>4
字節碼元
Rust的源碼文件.rs的默認文本編碼格式是UTF-8。
6、Rust附錄
字符串對象經常使用的方法