一文掌握鋼鐵是怎樣生鏽 -Rust- 的
關注「Rust 編程指北」,一起學習 Rust,給未來投資
五種我認爲值得掌握的現代編程語言:
-
C(競品:Zig): Unix/Linux / 基礎庫 等一大波老牌開源基礎庫和平臺開發
-
JavaScript(升級:TypeScript):瀏覽器 / NodeJS 後端 / 各種 App 內的 Web 開發,代表的是 Web 平臺
-
Python(競品:Julia):代表的是一大堆 AI 工具支持的腳本環境
-
Go:代表的是一部分的後端開發
-
Rust:代表的是替代了 C++ 的大規模底層開發,Rust 的開發能力覆蓋了 C++,但是又沒有 C++ 那一堆問題,擁有新的表達力和生命週期控制,並且它對 Web 平臺是對接的。
我刻意剔除了三種大語言 (僅在本文語境下討論,不限實際需求考慮):
-
Java(競品: Scala)
-
C++
-
C#
你認同麼?我認同,並且我認爲學校教了 C 語言之後,可以直接教 Rust(TODO: 這裏有一些支撐的理由,可以再討論)。
我也直接剔除了各種函數式語言:
-
scheme
-
Haskell
函數式語言的的一些範式一直被融入到主流語言裏面,日常開發也幾乎用不到函數式語言,在函數式語言裏面投入時間,邊際收益並不高,但你可以花一個暑假沉浸進去認真感受一次,這樣就夠了。
Rust 開發環境配置
安裝 rustup
-
Windows 本地安裝
-
下載 Windows 安裝包:https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe
-
Mac/Linux/Windows WSL 安裝
-
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
在線執行測試 | playground
https://play.rust-lang.org/
安裝 VSCode 插件
-
Rust support for Visual Studio Code
-
rust-analyzer
-
TOML Language Support
-
VS 左側搜索
file to exclude
可以配上**/lib*.json,
,在查找的時候忽略 Rust 自己生成的配置文件
掌握 Rust 的命令行工具鏈
-
rustup: 一般用來安裝 / 更新 rust 的版本,切換 stable 和 nightly 版本用
-
rustc:rust 的編譯器,一般不需要手工調
-
cargo:一般通過 cargo 來管理 rust 的 crate(rust 的包叫做 crate),同時 rust 的項目編譯管理都用 cargo,99% 的情況下,你只會需要 cargo 命令即可。
Rust 的工程結構
-
TOML
-
Cargo.toml
Rust 的模塊組織
img
上圖是 Rust 典型項目文件系統和對應的模塊系統,解釋如下:
Rust 項目根目錄聲明和導出模塊
-
Rust 項目,如果存在 main.rs,項目可以被編譯爲 bin 可執行文件
-
Rust 項目,如果存在 lib.rs,項目可以被其他項目作爲 lib 引用,其他項目在其 Cargo.toml 裏的
[dependencies]
裏指定my_project={path="../my_project"}
即可。 -
lib.rs 和 main.rs 是可選的,可以同時存在或者只有其中一個
-
在 main.rs 或者 lib.rs 文件內,通過如下的方式聲明當前目錄下存在的其他子模塊
mod config;
mod manager;
mod objects;
mod util;
- lib.rs 裏還可以指定導出哪些模塊:
mod config;
mod manager;
mod objects; // 含有mod.rs的子目錄是一個子模塊
mod util; // 含有mod.rs的子目錄是一個子模塊
pub use manager::*; // 指定全導出
pub use objects::AnyObject; // 指定導出objects模塊內的AnyObject
項目子目錄聲明和導出模塊
-
Rust 含有 mod.rs 的子目錄是一個子模塊
-
在子模塊的 mod.rs 內,例如 util 模塊,可以繼續
-
通過
mod path_util;
聲明子模塊 -
通過
pub use path_util::*;
導出 path_util 模塊內的所有可導出符號
使用其他模塊
-
Rust 項目根目錄的頂級模塊名爲
crate
-
Rust 項目根目錄下的一級模塊是
crate::xxxx
,因此引用時應該寫use crate::config::Config;
-
Rust 的子目錄下,例如 path_util 裏引用本級 cmd_util 有兩種方式
-
可以用
use crate::util::CmdUtil
從頂級模塊crate
開始指定路徑 -
也可以通過
use super::CmdUtil
指定。這是因爲path_util
和cmd_util
在模塊層級中的同級,可以通過super
來表示上一級模塊 -
如果 cmd_util 裏的 CmdUtil 是 pub 的,並且有導出 (例如 util/mod.rs 裏
pub use cmd_util::CmdUtil;
),那麼 path_util 裏
Rust 對象所有權 / 生命週期管理
Linuar Type: https://en.wikipedia.org/wiki/Substructural_type_system
Linear types corresponds to linear logic and ensures that objects are used exactly once, allowing the system to safely deallocate an object after its use.
-
Ownership: https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html
-
What is Ownership: https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html
-
References and Borrowing: https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html
-
The Slice Type: https://doc.rust-lang.org/book/ch04-03-slices.html
下面是幾個正交的維度 from : https://www.reddit.com/r/rust/comments/idwlqu/rust_memory_container_cheatsheet_publish_on_github/
Internal sharing? -[no]--> Allocates? -[no]--> Internal mutability? -[no]--> Ownership? -[no]-----------------------------------> &mut T
\ \ \ `-[yes]----------------------------------> T
\ \ \
\ \ `-[yes]-> Thread-safe? -[no]--> Internal references? -[no]---> Cell<T>
\ \ \ `-[yes]--> RefCell<T>
\ \ \
\ \ `-[yes]-> Internal references? -[no]---> AtomicT
\ \ \ `-[one]--> Mutex<T>
\ \ `--[many]-> RwLock<T>
\ \
\ `-[yes]------------------------------------------------------------------------------------> Box<T>
\
`-[yes]-> Allocates? -[no]-------------------------------------------------------------------------------------> &T
\
`-[yes]-> Thread-safe? -[no]---------------------------------------------------------------> Rc<T>
`-[yes]--------------------------------------------------------------> Arc<T>
C++ 生命週期回顧
C++ 從 C 繼承而來,對象生命週期的核心問題是:
-
對象生命週期
-
Stack 上對象的釋放,一旦超出對象的作用域,就自動 Destruct 對象。
-
Heap 上對象的釋放,需要手動 delete 來觸發 Destruct。
-
對象狀態管理
-
在一個線程內,對象可被多處持有,單線程的多處持有點都可能修改對象的狀態
-
在多個線程內,對象可被多線程持有,多線程可併發地修改對象的狀態
先看下對象的生命週期:
-
單線程
-
Stack 對象:
-
Heap 對象
-
多線程
-
Stack 對象
-
Heap 對象
再看下對象的狀態管理:
-
單線程
-
例如把一個類的成員變量直接暴露出去,到處使用,就會帶來封裝泄漏,違反單一修改點原則
-
不可變對象:可安全使用
-
可變對象:對象狀態需要被
封裝
才能處於儘量可控
-
多線程
-
併發修改對象,違反單一修改點原則
-
併發修改對象,如果不加互斥,會帶來對象的狀態修改處於
非原子
修改狀態,A 線程修改了一半,B 對修改了一半的髒數據
進行讀寫。 -
不可變對象:可安全使用
-
可變對象:對象狀態處於多線程共享時,需要有互斥機制,例如信號量和互斥鎖
Rust 所有權 Ownership
Rust 引入了一個核心的語義:所有權 (Owner),每個對象都有明確的所有權,所有權可以發生兩種變化,下面是核心規則:
-
移動 (
move
),例如
let x=String::from("test"); let y =x;
,賦值語句
let y=x;
將
x
的所有權移動給
y
,則
x
不再可用
-
需要注意的是,並不是賦值語句都發生了所有權的移動
-
內置類型 (
built in
) 會執行按位拷貝,例如let x = 6; let y = x;
-
實現了
Copy
這個trait
的類型,會進行深拷貝 -
可以看到在
Rust 裏拷貝不是默認的,爲了拷貝需要付出代價,這是根本性的設計和範式差異
:
-
實現
trait Copy
,則賦值會自動逐 bit 拷貝 -
實現
trait Clone
,則可以調用xx.clone()
獲得副本 -
借用 (
borrow
),將對象的所有權臨時借給其他對象,借完要還的!借用又分成兩種
-
【1】不可變借用 (
immutable borrow
):Rust 允許一個變量同時有多個不可變借用,例如let x=String::from("test"); let y = &x; let z=&x;
,則y
和z
都是x
的不可變借用 -
【2】可變借用 (
mutable borrow
):
Rust 只允許一個變量同時有一個可變借用
,例如
let x=vec![0;32]; let y=& mut x; let z=&mut x; y.push(0);
這裏 y 和 z 都發生了對 x 的可變借用,編譯器會報錯。
- 請在單線程限定下思考這樣設計解決了什麼問題?
Rust 內部可變性 (Internal mutability
)
有時候,我們需要【不可變借用的內部成員變量可變,在 Rust 裏面叫做內部可變性 (Internal mutability
)】。那麼,有如下選擇,它們內部都依賴底層的UnsafeCell
實現,顧名思義這麼做是unsafe
的,但是編譯器知道這些調用的地方需要特殊處理。
-
單線程
-
如果類型
T
實現了 traitCopy
,那麼可以使用Cell<T>
-
否則,可以使用
RefCell<T>
-
多線程
-
使用互斥鎖:
Mutex<T>
-
使用讀寫鎖:
RwLock<T>
Cell
對於實現了 Copy 的類型,可以使用 Cell<T>
,官方例子:https://doc.rust-lang.org/std/cell/struct.Cell.html
-
獲取:如果
T
實現了Copy
,則可以調用get
方法,獲得 T 的一份逐 bit 拷貝 -
設置:使用
set
方法 -
更新:使用
update
設置並返回新值 -
替換:使用
replace
方法 -
可變借用:使用
get_mut
方法獲得 Cell 變量的可變借用,該方法繼續遵循借用規則【1】【2】衝突原則。
改造下官方例子,官方例子裏只改變了一次不可變借用的 Cell 成員,稍加改造可以多次修改:
use std::cell::Cell;
struct SomeStruct {
regular_field: u8,
special_field: Cell<u8>,
}
fn main() {
let my_struct = SomeStruct {
regular_field: 0,
special_field: Cell::new(1),
};
// 第1次不可變借用
let x = &my_struct;
// 修改1
x.special_field.set(11);
println!("{}", x.special_field.get());
// 第2次不可變借用
let y = &my_struct;
// 修改2
y.special_field.set(3);
println!("{}", x.special_field.get());
// 修改3
x.special_field.set(10);
println!("{}", x.special_field.get());
}
RefCell
對於沒有實現Copy
的類型,例如String
和Vec<T>
,要實現多個不可變借用內部成員的可變性,就需要使用RefCell<T>
,常用方法主要是
-
獲得內部 T 的不可變借用:使用
borrow()
方法 -
獲得內部 T 的可變借用:使用
borrow_mut()
方法
雖然獲得了對不可變借用內部成員的可變修改能力,但是借用的規則【1】【2】依然起作用,下面是一組單元測試,注意 RefCell 的借用規則在編譯期不會檢查,但是運行期會檢查,如果違反會在運行期 panic。
測試 1:x 一旦 borrow_mut,就不可同時 borrow,借用規則【2】
fn test1(){
let x = RefCell::new(5);
let a = x.borrow();
let b = x.borrow_mut(); // 運行期 panic
}
測試 2:x 的 borrow 可多次,借用規則【1】
fn test2(){
let x = RefCell::new(5);
let a = x.borrow();
let b = x.borrow();
}
測試 3:y 是 x 的 clone,x 和 y 都可多次 borrow,遵循借用規則【1】
fn test3(){
let x = RefCell::new(5);
let a = x.borrow();
let b = x.borrow();
let y = x.clone();
let c = y.borrow();
let d = y.borrow();
}
測試 4:y 是 x 的 clone,x 和 y 一起,只能有一個 borrow_mut,借用規則【2】
fn test4(){
let x = RefCell::new(5);
let a = x.borrow_mut();
let y = x.clone();
let c = y.borrow_mut();// 運行期 panic
}
測試 5:y 是 x 的 clone,x 和 y 一起,可多次 borrow,借用規則【1】
fn test5(){
let x = RefCell::new(5);
let a = x.borrow();
let y = x.clone();
let c = y.borrow_mut();
}
測試 6:y 是 x 的 clone,x 和 y 一起,只能有一個 borrow_mut,借用規則【2】,可變借用在超出作用域後歸還,即可再次可變借用
fn test6(){
let x = RefCell::new(5);
let y = x.clone();
{
let a = x.borrow_mut();
}
let c = y.borrow_mut();
}
Mutex/RwLock
無論是 Cell 還是 RefCell,都是單線程語義下達到內部可變性的能力。在多線程情況下,同樣存在一個【不可變借用的內部成員變量可變】的需求。此時,就需要加鎖,Rust 的 Mutext/RwLock 不但實現了鎖的能力,同時提供了內部可變性的能力。
use std::task;
use std::sync::{Mutex, RwLock}
struct Test{
x: u32
}
// 使用Arc涉及到 內部共享(`Internal sharing`),參考後面
let v = Arc::new(Mutex::new(Test{x:10}))
let v1 = v.clone();
task::spawn(async move {
// 解鎖+獲得不可變借用
let v = v1.lock().unwrap();
});
let v2 = v.clone();
task::spawn(async move {
// 解鎖+獲得可變借用
let mut v = v1.lock().unwrap();
});
Rust 內存分配 (Allocate
)
Rust 的內存分配有三個區域
-
程序靜態區 (Static memory),一般是 static 對象
-
堆 (Heap), Box,Rc, Arc 以及大部分容器類型 String, Vec, VecDequeue, HashMap, BTreeMap 等,不能在編譯期確定大小
-
堆棧 (Stack),除了 #1,#2 外的其他所有 Value 對象都在程序堆棧(Stack) 上分配
Rust 跨線程傳遞 / 共享
對象在跨線程間使用
-
【1】一個對象可以從線程 A 傳遞給線程 B,此時需要對象類型實現
Send trait
-
【2】一個對象的借用可以從線程 A 傳遞給線程 B,此時需要對象類型實現
Sync Trait
- 如果
T
實現了Sync
,則& T
自動實現了Send
=>& T
可以從線程 A 傳遞給線程 B
根據上面的規則【1】,實際上一個對象從線程 A 傳遞給線程 B 有如下情況
-
原生指針即不實現
Send
, 也不實現Sync
-
Copy,既然都 Copy 了,每個線程持有一份獨立拷貝
-
Move,既然 Move 了,每次只有一個線程有所有權,
-
唯一所有權對象的 Borrow
-
不可變借用,多個線程間不可變借用,同時讀取,遵循可同時多處不可變借用規則
-
可變借用,一次只能有一個線程持有可變借用,唯一寫
-
多所有權對象的 Borrow
-
Rc
即不實現Send
也不實現Sync
,這是因爲Rc
的引用計數並沒有使用 Lock 或者 Aotomic,因此不能在多個線程間同時修改引用計數,不能在線程間 Send,更不能 Sync 了 -
Arc
實現了線程安全的引用計數,實現了Send
,如果內部包含的類型可以Sync
,則Arc<T>
也能Sync
-
UnsafeCell
沒有實現Sync
,因此Cell
和RefCell
也沒有實現Sync
,但是可以Send
Rust 內部共享 (Internal sharing
)
Rust 的有所有權唯一原則,但是有些時候,我們需要在多處持有一個不可變對象的所有權,這叫做內部共享 (Internal sharing
) 有兩種情況
-
單線程,此時,可以用 Rc,這是一個引用計數指針
-
多線程,此時,可以用 Arc,這是一個多線程安全的引用計數指針,
組合使用
單線程:
-
如果只需要唯一所有權
-
遵循 Copy/Move/Borrow 規則
-
如果需要多個所有權
-
使用 Rc
-
無論是單所有權還是多所有權,如果需要只讀對象的內部成員屬性可修改
-
Copy 類型使用 Cell
-
否則使用 RefCell
多線程:
-
如果只需要維持唯一所有權
-
只要 T 是 Send 的,就可以從線程 A 發送到線程 B
-
只要 T 是 Sync 的,就可以在線程 A 和線程 B 間,同時持有 & T,但是因此只能由一個 & mut T
-
如果需要維持多所有權
-
那麼需要 Arc,Arc 實現了 Send,如內部的 T 是 Sync 的,則 Arc 也是 Sync 的如果 T 是隻讀的就可以線程 A 發送到線程 B 也可以同時將 Arc 的 clone 對象發送給多個線程,此時由於多個線程都持有所有權,因此自然是多個線程共享了內部的 T 如果 T 是隻讀的,那麼 Arc 是 Sync 的,也就可以線程安全共享如果需要修改 T,Arc 不是 Sync 的,因此必須用 Arc 或者 Arc 製造只讀對象的內部可變性如果只是 T 的某個成員變量需要寫,那應該只要在那個成員變量上加 Mutex 即可,不必整個 T 都加 Mutex
Rust 的生命週期 (lifetime)
上面幾個小節都是 Rust 的所有權問題,本節討論 Rust 裏獨立的借用對象的生命週期標識符。
一、函數參數上的 lifetime 標記:(1)首先,Rust 的編譯器需要明確地知道一個借用對象是否還是有效的。例如返回一個新創建的對象肯定是有效的,不需要檢查。
fn create_obj():Object{
Object{}
}
(2)但是,顯然你不能返回一個局部對象的借用,因爲局部對象在函數結束後超出作用域就被釋放了:
fn get_obj():&Object{ // compile error
const obj = Object{};
&obj
}
(3)不過,如果這個借用本來就是從外部傳入的,那當然可以返回,函數結束後這個對象還是有效的:
// I am borrowed from caller
// return borrow to the caller is safe
fn process_obj(obj:&Object):&Object{
&obj
}
(4)然而,如果你傳入了兩個對象的借用,內部做了條件返回。那麼編譯器沒那麼智能,它並不總是能推斷出返回的是哪個對象的借用:
// compile error!
// where am I come from?
fn process_objs(x:&Obejct, y:&Object):&Object{
if(x.is_ok()){
&x
}else{
&y
}
}
(5)因此,Rust 保留了內部的一種編譯器內部的,本來是隱式添加記號,也就是生命週期 (lifetime),通過顯式添加生命週期標記,解決上述問題:
// I am come from 'a lifetime, NOT 'b
fn process_objs<'a,'b>(x: &'a Obejct, y:&'b Object):&'a Object{
&x
}
// I am come from 'a lifetime, x,y,and result are all 'a lifetime
fn process_objs<'a>(x: &'a Obejct, y:&'a Object):&'a Object{
if(x.is_ok()){
&x
}else{
&y
}
}
(6)事實上,當你沒寫 lifetime 標記時,每個對象也都是有對應的 lifetime 的,例如編譯器爲每個對象生成一個不同的 lifetime
fn test<'a,'b>(x: &'a Obejct, y:&'b Object){
}
(7)因爲默認生成的都是不同的,所以返回值如果不標記是誰,編譯器就無法推斷:
fn test<'a,'b,'c>(x: &'a Obejct, y:&'b Object):&'c Object{ // 'c is 'a or 'b ?
if(x.is_ok()){
&x
}else{
&y
}
}
(8)所以如果我們顯式標記,並讓兩個變量用同一個,就能解決,這就是告訴編譯器,'c='a='b
:
fn test<'a>(x: &'a Obejct, y:&'a Object):&'a Object{ // 'c='a='b, they are all 'a
if(x.is_ok()){
&x
}else{
&y
}
}
(9)看到這裏,你也應該知道了 lifetime 標記的名字是任意的,只是一個【形參】,代表的是這個借用對象的生命週期作用域的名字:
{
let obj; //---'a start here
{
let x = Obj{}; //---'b start here
obj = &x; //---'b finish here
}
println!("obj: {}", obj); //---'a finish here, 'b is out of scope, compile error!
}
// #[derive(Apparition)]
{
// 當然你可以用任意合法的符號替換'a和'b,它們只是個名字
let obj<'b>; //---'a start here
{
let x = Obj{}; //---'b start here
obj<'b> = &'b x; //---'b finish here
}
// obj借用是否有效,僅僅取決於它實際上它所借用的對象的生命週期作用域'b範圍是否大於等於'a
// 一個'b作用域內的對象的借用,在'a內被調用,但是'b比'a小,調用的時候'b已經不存在了
// 因此編譯器宣佈:這是非法的。
println!("obj: {}", obj<'b>); //---'a finish here, 'b is out of scope, compile error!
}
二、結構體成員的 lifetime 標記:在 Rust 裏面一個結構體的成員變量如果是一個外部對象的借用,那麼必須標識這個借用對象的生命週期
struct Piece<'a>{
slice:&'a [u8] // 表明slice是來自外部對象的一個借用,'a只是一個生命週期形參
}
// Piece的定義裏面,'a 表示vec的生命週期,
// 下面的例子調用,vec的生命週期至少應該大於等於piece的生命週期
// 簡單說vec存活的作用域應該大於等於piece的存活作用域
fn test(){
let vec = Vec::<u8>::new();
let piece = Piece{slice: vec.as_slice()};
}
// 下面就是錯的, piece返回後,vec已經掛了
// 不滿足vec的生命週期大於等於piece的生命週期這條
fn test_2()->Piece{
let vec = Vec::<u8>::new();
let piece = Piece{slice: vec.as_slice()};
piece // compile error: ^^^^^ returns a value referencing data owned by the current function
}
如果有兩個不同的成員,分別持有外部對象的借用,那麼他們應該使用一個生命週期標識還是兩個呢?
struct Piece<'a>{
slice_1: &'a [u8], // 使用相同的生命週期標識
slice_1: &'a [u8], //
}
// Piece的定義裏面,'a只是表示slice_1和slice_2所借用的對象的存活範圍在一個相同的作用域內,
// 而不是說slice_1和slice_2所借用的對象必須是同一個,區分這點很重要
fn test_1(){
// slice_1 和 slice_2 借用了同一個對象vec
let vec = Vec::<u8>::new();
let piece = Piece{slice_1: vec.as_slice(), slice_2: vec.as_slice()};
}
fn test_2(){
// slice_1 和 slice_2 借用了兩個不同的對象
let vec_1 = Vec::<u8>::new();
let vec_2 = Vec::<u8>::new();
let piece = Piece{slice_1: vec_1.as_slice(), slice_2: vec_2.as_slice()};
}
// 如果所借用的兩個對象的存活返回不同,'a只會取他們生命週期的最小的交集
// 下面這個例子,'a 和 vec_1的作用域相同
fn test_3(vec_2:&Vec<u8>){
// slice_1 和 slice_2 借用了兩個不同的對象
let vec_1 = Vec::<u8>::new();
let piece = Piece{slice_1: vec_1.as_slice(), slice_2: vec_2.as_slice()};
}
// 因此,如果把piece返回就會出錯,因爲piece的生命週期不能超過vec_1
fn test_4(vec_2:&Vec<u8>)->Piece{
// slice_1 和 slice_2 借用了兩個不同的對象
let vec_1 = Vec::<u8>::new();
let piece = Piece{slice_1: vec_1.as_slice(), slice_2: vec_2.as_slice()};
piece
// compile error: ^^^^^ returns a value referencing data owned by the current function
}
// 顯然,稍加改造就可以:
fn test_5<'a>(vec_1:&'a Vec<u8>, vec_2:&'a Vec<u8>)->Piece<'a>{
// slice_1 和 slice_2 借用了兩個不同的對象
let piece = Piece{slice_1: vec_1.as_slice(), slice_2: vec_2.as_slice()};
piece
}
三、結構體成員函數的 lifetime 標記:
結構體成員函數和普通函數一樣,可以有生命週期標識
struct Range{
start: usize,
len: usize
}
impl Range{
// 接受一個外部的Vec對象的借用作爲參數
// 返回這個Vec的片段的一個借用
// 因此,需要引入生命週期標識
// 表明返回的&[u8]的生命週期和傳入的owner的生命週期一致
pub fn as_slice<'a>(&self, owner: &'a Vec<u8>)->&'a [u8] {
let slice = &owner[self.start..self.end()];
slice
}
}
下面的代碼會出錯:
enum AdvancedPiece{
Range(Range),
Vec(Vec<u8>)
}
impl AdvancedPiece{
pub fn as_slice<'a>(&self, owner: & 'a Vec<u8>)->&'a [u8] {
match self {
AdvancedPiece::Range(range)=>{
range.as_slice(owner) // range.as_slice(owner)返回的&[u8]生命週期和owner一致,用'a標記
},
AdvancedPiece::Vec(vec)=> {
&vec // compile error: &vec的生命週期和owner並不一致
}
}
}
}
結構體生命週期標識的一個需要注意的地方是,&self 也是可以標註生命週期的,因爲 & self 本身也是一個借用,既然是借用,就可以標記生命週期。從這個角度也可以進一步理解,生命週期就是標記借用對象的存活作用域用的。上述代碼,實際上等價於:
enum AdvancedPiece{
Range(Range),
Vec(Vec<u8>)
}
impl AdvancedPiece{
// self有自己獨立的生命週期,用獨立的生命週期標識'b 標記出來
// 這樣就看得更清楚了
pub fn as_slice<'a,'b>(&'b self, owner: & 'a Vec<u8>)->&'a [u8] {
match self {
AdvancedPiece::Range(range)=>{
range.as_slice(owner) // range.as_slice(owner)返回的&[u8]生命週期和owner一致,用'a標記
},
AdvancedPiece::Vec(vec)=> {
&vec // compile error: &vec的生命週期是'b , 返回值需要的是'a
}
}
}
}
因此,我們可以標記 & self 和 owner 的生命週期是一致的來向編譯器說明需求:
enum AdvancedPiece{
Range(Range),
Vec(Vec<u8>)
}
impl AdvancedPiece{
// 約定調用as_slice在self和owner的生命週期交集'a內是合法的
pub fn as_slice<'a>(&'a self, owner: & 'a Vec<u8>)->&'a [u8] {
match self {
AdvancedPiece::Range(range)=>{
range.as_slice(owner) // range.as_slice(owner)返回的&[u8]生命週期和owner一致,用'a標記
},
AdvancedPiece::Vec(vec)=> {
&vec // 此時,&vec的生命週期也是'a
}
}
}
}
四、省略生命週期標識 / 匿名生命週期標識:
上述代碼裏面,Rust 在帶有生命週期標識的函數或者結構體調用的時候,允許省略顯式寫生命週期標識,就像泛型參數在編譯器可以自動推導類型時可以省略一樣:
fn args<T: ToCStr>(&mut self, args: &[T]) -> &mut Command // elided
fn args<'a, 'b, T: ToCStr>(&'a mut self, args: &'b [T]) -> &'a mut Command // expanded
下面是結構體使用中省略生命週期標識的例子
struct Piece<'a>{
slice:&'a [u8] // 表明slice是來自外部對象的一個借用,'a只是一個生命週期形參
}
fn create_piece_1<'a>(vec:&'a Vec<u8>)->Piece<'a>{
Piece{slice:&vec}
}
fn create_piece_2(vec:&Vec<u8>)->Piece{
Piece{slice:&vec}
}
但是,有的時候,我們希望顯式表示生命週期,讓代碼更 “清晰”,可以用匿名生命週期
fn create_piece_3(vec:&Vec<u8>)->Piece<'_>{ // '_ 標記返回值Piece的生命週期參數,但是不必在函數和參數裏面標記生命週期
Piece{slice:&vec}
}
同樣的,結構體的 impl 裏也可以用匿名生命週期簡化代碼:
impl<'a> Piece<'a>{
fn create_piece_4(vec:&'a Vec<u8>)->Piece<'a>{
Piece{slice:&vec}
}
}
impl Piece<'_>{
fn create_piece_5(vec:&Vec<u8>)->Piece<'_>{
Piece{slice:&vec}
}
}
五、結構體的一個成員變量借用另一個成員變量的情況:
// TODO(先寫一個使用 Buffer/Pieces 的例子)
Rust 裏的 OO 編程
- What is OO:
https://doc.rust-lang.org/book/ch17-01-what-is-oo.html
-
數據和行爲:Objects Contain Data and Behavior
-
封裝:Encapsulation that Hides Implementation Details
-
多態:Polymorphism
To many people, polymorphism is synonymous with inheritance. But it’s actually a more general concept that refers to code that can work with data of multiple types. For inheritance, those types are generally subclasses. Rust instead uses generics to abstract over different possible types and trait bounds to impose constraints on what those types must provide. This is sometimes called bounded parametric polymorphism.
其中,傳統 OO 裏多態是運行時多態,常規的實現是通過繼承來達成的:Inheritance as a Type System and as Code Sharing ,但是繼承共享代碼一般會導致三種問題:
-
父類不匹配子類的行爲
-
子類不匹配父類的行爲
-
共享了過多的數據和行爲,導致了緊耦合
從 C++ 的模版編程 + Concept 概念開始,泛型 + 萃取這種編譯期,通過兩種不同的抽象維度來實現多態,叫做:bounded parametric polymorphism.
-
泛型 (generic):抽象不同類型
-
萃取 (trait): 約束了泛型應該提供的能力
Rust 在 OO 編程上的選擇,採用的正是完備的編譯期 OO + 多態設計:
-
通過 struct 抽象數據,語法是
struct Data{}
-
通過爲 struct 提供實現抽象行爲,是否
pub
用來控制行爲的封裝,語法是impl Data{ fn method(){} }
-
通過是否公開數據和行爲來控制封裝細節,但是一般來說除非一個對象是用來做 POD(Plain Old Data) 的純 Component,一般數據結構的字段不應該暴露:
-
pub struct Data{ pub no: u32}
-
impl Data{ pub fn new()->Data{Data{no:0}}
-
通過泛型抽象不同類型:
fn test<T>(t:T){}
-
可以通過在泛型上添加約束,表面這個泛型實現了哪些 trait,例如:
fn test<T> where T: Clone{}
-
通過 trait 抽象類型必須擁有的能力:*
trait Echo{ fn echo();}
*impl Echo for Data{ fn echo(){}}
*fn test<T>(t:T) where T:Echo {}
-
這裏的特點是,你可以爲一個 struct 提供不同的 trait,例如:*
trait Clone{ fn clone()->Self;}
*trait Echo{ fn echo()->Self;}
*impl Clone for Data{ fn copy()->Self{...}}
*impl Echo for Data{ fn echo(){...} }
-
這和傳統 OOP 爲一個 class 提供多個 interface 抽象並不相同 *
trait
和interface
本身都是正交抽象的,一個抽象只做一件事 *trait-struct
是通過外掛
方式提供抽象,而interface-class
是通過繼承
方式提供抽象,這意味着當你不引入一個爲某個struct
提供的trait
時,你看不到該struct
的外掛,而interface
則是耦合在 class 的實現裏。*trait
是編譯期多態,interface
是運行期多態
Rust 靜態分發 (Static Dispatch
)
Rust 基於 Trait 實現靜態分發,所謂靜態分發就是指在編譯期實現多態。
情景 1:
trait Echo{
fn echo(&self);
}
struct Test{
}
struct Test2{
}
impl Echo for Test{
fn echo(&self){
}
}
impl Echo for Test2{
fn echo(&self){
}
}
fn do_something(t:&impl Echo){
}
fn get_something(value:bool)->impl Echo{
if value {
Test{}
}else{
Test2{}
}
}
let t = Test{}
do_something(&t);
let v = get_someting(false); // 編譯錯誤
這裏的impl Echo
只是一個簡寫,編譯器會確定 t 的具體類型,但是一次調用中類型是唯一確定的,並不能動態切換,因此do_something()
可以正確被靜態確定 t 的類型,但是get_something()
編譯會出錯,因爲->impl Echo
並不是說可以返回【任意實現了 Echo 的類型】,而只是一個簡寫,函數體內必須返回同一種類型。如果需要【任意實現了 Echo 的類型】,應該做成泛型:
fn get_somethig<T:Echo>(value:bool)->T{ //不過使用的地方如果編譯器不能推導出T的類型,應該明確指定T的類型
if value {
Test{}
}else{
Test2{}
}
}
大部分時候,靜態分發都是和泛型一起使用的:
fn test<T,U>(t:&T)-> where T:Echo+Clone+Debug, U:Echo+Display{
}
這裏的Echo+Clone+Debug
屬於【Intersect Type】也就是 T 需要同時實現這幾個 Trait,泛型和 Trait 的配合是 Rust 靜態分發的基本範式。
Rust 動態分發 (Dynamic Dispatch
)
動態分發,就是和傳統 OOP 那樣,在運行期才能確定類型,編譯器在編譯期只能確定其 Trait 類型。但是由於只知道 Trait 信息,無法確定具體類型,就不能確定類型的確定性大小,因此不能在 Stack 上分配對象,需要用 Box 包一層,T 分配在 Heap 上。Box 指針則是確定性大小的,指針本身分配在 Stack 上。又爲了避免 Box 的含義的混淆,語法上需要加dyn
關鍵字:Box,例如
fn test(t: Box<dyn Echo>){
}
參考:[1] https://blog.rust-lang.org/2015/05/11/traits.html
Rust 的閉包
一句話說明 Rust 的閉包:閉包的本質是編譯器幫你生成了一個實現 (impl) 了 Fn/FnMut/FnOnce 等 Trait 的匿名 struct
Rust 容器和函數式編程
-
std::collections
-
Sequences: Vec, VecDeque, LinkedList
-
Maps: HashMap, BTreeMap
-
Sets: HashSet, BTreeSet
-
Misc: BinaryHeap
-
Rust Iterators
-
Iteration
-
Mapping
-
Filtering
-
Folding
-
Collecting
-
Composing
-
Some Real World Code
Rust 的類型設計
-
原子類型
-
bool
-
u8/u16/u32/u64/u128
-
i8/i16/i32/i64/i128
-
usize
-
整型:
-
布爾:
-
struct XXX{}
結構體類型 -
enum C{ A(u32), B(String) }
枚舉類型 (帶 tag 的 Union),配合模式匹配使用 -
union
聯合類型(C 風格無 tag 的 Union),Unsafe 下配合模式匹配使用 -
tuple, (a,b,c)
-
unit
類型:()
, 只有唯一的值()
-
new type: struct XXX();
-
可以看成是【有名字的單元素 tuple 類型】,例如 struct MyString(String); 構造:
let name = MyString(String::new("xxx"))
或者let name = MyString{0:String::new("xxx")}
使用:println!(name.0)
-
new type 的目的是製造真正的新類型,如果使用
type MyString=String;
只是製造了一個別名。而使用 new type 則是製造了一個新的獨立類型,代價是內部嵌套的類型的方法和屬性都必須在新類型上重新導出纔可以直接被外部使用,否則就得通過 xxx.0 先獲取內部類型再調用。很多時候 new type 可以解決封裝問題和孤兒原則問題 (TODO:如有必要此處可詳細展開)。 -
trait: TypeClass
-
字符串:
-
String
, 堆上分配內存 -
&str
,String 的 Slice 類型 -
容器
-
Vec<T>
,堆上分配內存 -
&[T]
, Vec 的 Slice 類型 -
指針
-
借用:
& T
,&mut T
-
內部可變性:
Cell<T>
,RefCell<T>
,Mutex<T>
,RwLock<T>
-
引用計數:
Rc<T>
,Arc<T>
-
裝箱:
Box<T>, Pin<T>
-
底類型 (
nerver
):!
Rust 模式匹配
枚舉類型配合模式匹配使用是最佳搭檔
enum Test{ A(i32), B(String) }
let t = Test::A(0);
match t {
Test::A(v)=>{},
Test::B(v)=>{}
}
Rust 錯誤處理
錯誤處理可以用if
模式匹配:
fn test()->Result<T,Error>{}
let ret = test();
if let Err(e) = ret {
}
let value = ret.unwrap();
可以用直接模式匹配:
fn test()->Result<T,Error>{}
match test() {
Ok(value)=>{},
Err(e)=>{}
}
但是最常用用的是錯誤可選的錯誤類型映射 + 問號求值,錯誤處理不再卡殼主線流程:
fn test()->Result<String,Error>{
}
fn other()->Result<String, OtherError>{
let value = test().map_err(|err|{
// 錯誤類型轉換,同類型就不需要轉換
Err(OtherError::from(err))
})?; // 問號求值,如果出錯就直接返回錯誤,規避了其他語言的各種if err 處理
// do something...
Ok(value)
}
Rust 多線程編程
-
鎖,鎖是一種製造多線程安全的內部可變性的指針
-
Mutex/RwLock
-
有同步版本和異步版本
-
引用計數,引用計數是一種製造多所有權的指針
-
單線程用 Rc
-
多線程用 Arc
Rust 異步編程
https://book.async.rs/introduction.html
在當前 Executor 裏發起一個異步任務
use async_std::task;
task::spawn(async move {
});
在一個線程裏發起異步任務
use async_std::thread;
thread::spawn(move ||{
task::block_on(async { {
});
})
示例的鏈式異步 + 錯誤處理 + 異步 + 錯誤處理...
let v = fetch().await.map_err(|err|{...})?.another_fetch().await.map_err(|err|{...})?;
本質上並不存在【真異步】,所有的異步都是僞裝出來的,本質上【異步 = 獨立開一個線程循環輪詢】
-
獨立開一個線程,循環輪詢操作系統相關的事件,例如 socket,這種輪詢方式被叫做 Reactor 模式,每次輪詢的時候問下系統是否有新的可用事件 (Event)
-
獨立開一個線程,循環輪詢一個
Future
,這種輪詢是 Executor 做的。
-
所謂
Future
就是提供了一個poll
方法的對象,每次輪詢的時候調用一次 poll, 如果狀態位 Ready,就結束從隊列裏移除該 Future,否則繼續。 -
而 poll 的實現裏面,如果不返回 Ready,就需要返回 Pending 同時持有下傳遞進來的一個 waker 對象,在數據準備好的時候調用下 waker.wake(),通知 Executor 可以再次輪詢。
-
如果 poll 實現裏剛好是一個和 socket 相關的操作,就要做下 wake 和 socket 相關的 Event 之間的一個映射,這樣 Reactor 裏的輪詢到 event 的時候,就會找到影視的 waker,調用 wake,從而通知到 Executor 再次輪詢。
-
Future 是可組合的,
await
是組合 Future 的語法糖。一直組合到 main 函數,返回一個頂層的 Future,被反覆輪詢。
async/await
提供了魔法,但是拆開盒子又沒有魔法,這是編程的核心樂趣所在。
如何寫一個定時器泵:
use async_std::prelude::*;
use async_std::stream;
use std::time::Duration;
let mut interval = stream::interval(Duration::from_secs(4));
while let Some(_) = interval.next().await {
println!("prints every four seconds");
}
如何寫一個可調度的定時器泵:
// 創建一個channel
let (cmd_sender, cmd_recver) = async_std::sync::channel(8);
// 異步創建一個泵
async_std::task::spawn(async move {
loop {
let cmd_recver = ctx.cmd_recver.clone();
let cmd = async_std::io::timeout(Duration::from_millis(500), async move {
cmd_recver.recv().await.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))
}).await;
// 此時要麼過了500毫秒,要麼cmd_recver收到了一個cmd_sender投遞的信號
}
});
// 在其他地方調度
cmd_sender.send(());
Rust 日誌組件
use log::*;
use simple_logger;
fn main(){
simple_logger::SimpleLogger::new().with_level(LevelFilter::Debug).init().unwrap();
info!("{}",1000);
warn!("{}",1000);
error!("{}",1000);
debug!("{}",1000);
}
Rust 常用的設計模式
Builder 模式:
pub struct Object{
name: String,
id: Option<u32>,
email: Option<String>
}
impl Object{
pub fn new(name:String)->ObjectBuilder{
ObjectBuilder::new(name)
}
}
pub struct ObjectBuilder{
name: String,
id: Option<u32>,
email: Option<String>
}
impl ObjectBuilder{
pub fn new(name:String)->Self{
// Builder的構造函數只傳入必須有的字段
Self{
name,
id:None,
email: None,
}
}
pub fn id(mut self, id:u32>)->Self{
// 設置可選字段,注意self的所有權進來又出去
self.id = Some(id);
self
}
pub fn email(mut self, email:String)->Self{
// 設置可選字段,注意self的所有權進來又出去
self.email = Some(email);
self
}
pub fn build(self)->Object{
// 構造Obejct,注意self的所有權進來,成員都被move給了Object,self所有權結束使用
Object{
name: self.name,
id: self.id,
email: self.email
}
}
}
// 使用
let obj = Object::new(String::from("fanfeilong")).id(13u32).email(String::from("fanfeilong@example.com")).build();
Split 模式:
pub struct Object{
name: String,
data: Vec<u8>
}
pub struct ObjectMore{
name: String,
id: Option<u32>,
email: Option<String>
}
impl Object{
// 消耗掉self的所有權,返回成員元組
pub fn split(self)->(String, data){
(self.name, self.data)
}
}
// 消耗掉Object,將其成員Move給ObjectMore,同時對data做進一步的細化轉換,保持最小內存分配開銷
let obj = Object{..}
let (name, data) = obj.split();
let (id, email) = decode(data);
let obj_more = ObjectMore{name, id, email);
消除 lifetime 傳染
例如有如下的帶 lifetime 的 trait
pub trait RawDecode<'de>: Sized {
fn raw_decode(buf: &'de [u8]) -> BuckyResult<(Self, &'de [u8])>;
}
爲它擴展一個 trait 時,生命週期會傳染到上層,需要外層傳入 buf:
pub trait FileDecoder<'de>: Sized {
fn decode_from_file(file: &Path, buf: &'de mut Vec<u8>) -> BuckyResult<(Self, usize)>;
}
impl<'de,D> FileDecoder<'de> for D
where D: RawDecode<'de>,
{
fn decode_from_file(file: &Path, buf: &'de mut Vec<u8>) -> BuckyResult<(Self, usize)> {
match std::fs::File::open(file) {
Ok(mut file) => {
// let mut buf = Vec::<u8>::new();
if let Err(e) = file.read_to_end(buf) {
return Err(BuckyError::from(e));
}
let len = buf.len();
let (obj, buf) = D::raw_decode(buf.as_slice())?;
let size = len - buf.len();
Ok((obj, size))
},
Err(e) => {
Err(BuckyError::from(e))
},
}
}
}
可以通過在 where 字句中使用 for 表達式來阻斷生命週期傳染,因爲我們可以確定 buf 的生命週期在函數內時夠用的:
pub trait FileDecoder2: Sized {
fn decode_from_file(file: &Path) -> BuckyResult<(Self, usize)>;
}
impl<D> FileDecoder2 for D
where D: for<'de> RawDecode<'de>,
{
fn decode_from_file(file: &Path) -> BuckyResult<(Self, usize)> {
match std::fs::File::open(file) {
Ok(mut file) => {
let mut buf = Vec::<u8>::new();
if let Err(e) = file.read_to_end(&mut buf) {
return Err(BuckyError::from(e));
}
let len = buf.len();
let (obj, buf) = D::raw_decode(buf.as_slice())?;
let size = len - buf.len();
Ok((obj, size))
},
Err(e) => {
Err(BuckyError::from(e))
},
}
}
}
使用 trait 的關聯類型替代泛型:
pub trait DescType{
fn type()->u32;
}
pub trait Object{
type Desc: DescType;
fn type_info()->String{
let type = Desc::type();
type.to_string()
}
}
pub struct RealDescType{
}
impl DescType for RealDescType{
fn type()->{ 0u32 }
}
pub struct RealObject{
}
impl Object for RealObject{
type Desc = RealDescType;
}
// 可以在泛型裏使用Object,以及Object關聯的Desc類型
pub struct ObjectDescript<O:Object>{
instance: O,
desc: O::Desc, // 則Desc可以跟隨O發生變化,這屬於編譯期多態
}
// 可以根據Desc是否實現了某些Trait來爲ObjectDescript自動實現某些Trait
// 例如,如果O::Desc實現了Debug,則自動爲ObjectDescript<O>實現Debug
impl Debug for ObjectDescript<O> where O: Object, O::Desc: Debug{
}
泛型組合的方式的代碼複用
泛型成員變量可以達到基於組合來做基類 / 子類的能力,子類變成了一個需要被組合的泛型類型參數,例如:
pub trait Sub{
type Desc: ObjectDesc;
}
pub struct Base<Content:Sub>{
name: String,
desc: Content::Desc, // 子類通過關聯類型來【定製】父類的某些關鍵成員變量的類型,但是該成員變量的佈局是放在父類這裏,子類Content本身不需要持有desc。
content: Content // 直接嵌入的子類部分數據
}
這裏的父類 / 子類,只是一個兼容傳統 OOP 的說法,實際上這裏都是泛型類。
消除循環依賴,規避所有權複雜度
如果 A 和 B 互相依賴
pub struct A{
b: B
}
pub struct B {
a: A
}
拆解出一個共同的部分 C 來消除依賴:
pub struct C {
}
// 讓A和B共同依賴C,A和B之間保持線性依賴
pub struct A {
c: C,
b: B
}
// 則A裏面需要被B調用的方法只要做成非成員方法即可:
impl A{
pub fn call(c: &C){
}
}
pub struct B {
c: C
}
impl B {
pub fn some(&self){
// B根本不需要持有A,只要有C就可以調用A,或者call直接就是C的方法即可
A::call(&self.c)
}
}
事件系統
/// ## 定義一個訂閱回調Trait
#[async_trait]
pub trait FnSubscriber: Send + Sync + 'static {
async fn call(&self, topic_id: TopicId, device_id:DeviceId) -> BuckyResult<()>;
}
/// ## 自動從Fn轉型爲FnSubscriber
#[async_trait]
impl<F, Fut> FnSubscriber for F
where
F: Send + Sync + 'static + Fn(TopicId, DeviceId) -> Fut,
Fut: Future<Output = BuckyResult<()>> + Send + 'static,
{
async fn call(&self, topic_id: TopicId, device_id:DeviceId) -> BuckyResult<()> {
let fut = (self)(topic_id, device_id); // 直接調用F:Fn(TopicId, DeviceId)
let res = fut.await?; // 異步等待
Ok(res.into()) // 返回結果
}
}
pub struct Test{
subscribers: Vec<Arc<dyn FnSubscriber>>, //動態分發
}
impl Test{
pub fn new()->Self{
Self{
subscribers: Vec::new(),
}
}
// 註冊事件
pub fn on_subscribe(&mut self, callback: impl FnSubscriber){
self.subscribers.push(Arc::new(callback));
}
// 觸發事件
async fn emit_subscribe(&self, topic_id: &TopicId, device_id:&DeviceId)->BuckyResult<()>{
for callback in self.subscribers.iter() {
callback.call(topic_id.clone(), device_id.clone()).await?;
}
Ok(())
}
}
異步編程中,Arc 和 Mutex 的正確用法
首先看下 Arc 和 Mutex 的正確配合:
-
需要多線程共享所有權的對象,一律用 Arc 即可
-
Arc 導致 T 是隻讀的,但是你肯定需要修改某些成員變量
-
難道就直接 Arc 麼?每次使用的時候 obj.lock().unwrap().member = xxx?
-
No!粒度太大,
只應該在T的需要被修改的成員變量上加Mutex
-
如果那個成員變量也不是葉子節點,還有內部的結構,應該繼續【下推】到 T 的需要修改的成員變量上去添加 Mutex
例如:
// 頂層類型是個Arc<T>的封裝,使用new type的方式包裝一層
// Something可以被安全都在多線程task裏clone後傳遞
struct Something(Arc<Something>);
impl Something(Arc<Something>){
new(y:String,a:String,p:u32)->Self{
return Self{
0:SomethingInner{y,x:Other{a,b:Third{p,q:Mutex::new(Vec::new())}}}
}
}
// TODO:在此添加暴露SomethingInner方法給外部的成員函數,這個重複是必要的
}
struct SomethingInner{
y: String;
x: Other;
}
struct Other{
a: String;
b: Third;
}
struct Thrid{
p: u32;
q: Mutex<Vec<String>>; // 如果只有這個需要修改,只需這裏加Mutex
}
impl Third{
fn append(&self, e:String){
self.q.lock().unwrap().push(e); // 通過Mutex的內部可變性來修改q
}
}
其次,我們看下同步鎖和異步鎖
-
Rust 的同步庫裏面有同步的鎖:
std::sync::Mutex
-
Rust 的 async_std 裏有一個異步鎖:
async_std::sync::Mutex
-
它們的區別是
async_std::sync::Mutex
實現了Send
接口,因此可以跨越await
點,例如:
async fn append(&self, e:String){
// 獲取異步鎖的Guad對象
let list = self.q.lock().await().unwrap(); // 通過Mutex的內部可變性來修改q
// 異步調用點
// 調度器會可能會在此處返回後下次再次進入到這裏繼續後面的執行,
// 兩次執行可能不在一個線程
waint().await();
// 使用異步鎖的Guad對象
// 這裏可能和list獲取時不在一個線程,因此,list需要實現`Send`
// 同步鎖無此能力
list.push(e);
}
但是,上述做法大部分時候時錯的。原因在於異步鎖改變了鎖的作用:
-
在同步鎖的時候,只是用同步鎖來【鎖定對變量的讀寫修改】這個最小粒度
-
在異步鎖的時候,鎖被用來鎖定了一堆異步行爲,這【擴大了鎖的粒度】,以及【延遲了鎖的釋放時機】
-
上述第 2 點導致了性能可能出現巨大劣化。
-
最重要的是這沒必要,大部分時候你只需在【對變量做原子修改時加同步鎖即可】
-
如果你需要【鎖定多個行爲】,此時你需要的不是鎖,而是在【使用同步鎖做入口控制】,類似 SQL 語句裏,使用表的主鍵在入口處做併發防護。
-
再往下,如果你需要保證一堆操作要麼實現,要麼都不實現,此時你需要的是【事務】。
-
簡單說,大部分時候,不要使用
async_std::sync::Mutex
通過 Trait 來擴展 Trait
Rust 的孤兒原則導致,如果一個 struct S 和一個自定義 trait T 都不在該項目中,無法使用 T 爲 S 添加擴展,也無法爲 S 提供新的 impl。因此,可以通過定義一個新的在本項目裏的 trait,來爲某個不在本項目裏的 struct 實現擴展,也可以是爲實現了某個 Trait 的泛型提供擴展。
Rust 應用開發
-
Rust Web 開發,Rust 可直接寫 HTML,編譯成 wasm,組件的寫法和 React 幾乎一樣。這樣增加了超強強類型 + 性能優勢,同時有 React 同等級別的生產力。
-
http://www.sheshbabu.com/posts/rust-wasm-yew-single-page-application/
Rust 工具鏈
-
Rust 編譯速度優化 (How to speed up the Rust compiler some more in 2020)
-
https://blog.mozilla.org/nnethercote/
Rust 社區
- https://www.reddit.com/r/rust/
參考資料
[1] 給 Java/C#/C++ 程序員的 Rust 介紹,這種教程風格是我最喜歡的,通過 Diff,Step By Step 引入概念設計上的不同:https://fasterthanli.me/articles/i-am-a-java-csharp-c-or-cplusplus-dev-time-to-do-some-rust
[2] 作者學習 Rust 遇到的一個個怪 (阻撓,Frustrat),但是作者一步步打怪升級,把概念喫的很透徹:https://fasterthanli.me/articles/frustrated-its-not-you-its-rust
[3] Rust 小抄:https://www.programming-idioms.org/cheatsheet/Rust
[4] Rust 引入了一堆概念,可以看看王垠對 Rust 的設計上的一些問題的評價:http://www.yinwang.org/blog-cn/2016/09/18/rust
[5]why rust is meant to replace c https://evrone.com/rust-vs-c
[6] awesome-rust: rust 常用庫大全 https://github.com/rust-unofficial/awesome-rust
[7] 使用 vector-index 方式構造 graphs 數據結構 http://smallcultfollowing.com/babysteps/blog/2015/04/06/modeling-graphs-in-rust-using-vector-indices/
[8] 使用 rc > 方式構造 graphs 數據結構 https://github.com/nrc/r4cppp/blob/master/graphs/README.md
[9] Rust Async Book,看這個文檔就夠了:https://rust-lang.github.io/async-book/01_getting_started/01_chapter.html
原文鏈接:https://www.cnblogs.com/math/p/rust.html 作者 範飛龍
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/IgtIr1W3IxHKZCsMx0EARg