rust learning

day 1 (2021/05/27)

學了常量,變數,資料型別,控制流,所有權

  1. char 的寬度是4位元組,一個 unicode 的寬度
  2. 控制流條件都不要括號
  3. rust 中的元組使用和 c++ 中的非常相似
    // clang++ test.cpp -std=c++11 && ./a.out
    #include <iostream>
    #include <string>
    #include <tuple> int main() {
    std::tuple<int, std::string> t = std::make_tuple(12, "zxh");
    std::cout << std::get<0>(t) << " " << std::get<1>(t) << std::endl; std::string s;
    int i;
    std::tie(i, s) = std::make_tuple(1, "test-tie");
    std::cout << i << " " << s << std::endl;
    } // 輸出
    12 zxh
    1 test-tie
  4. 所有權
    • 定義非常清晰嚴格,在作用域結束時生命週期結束,賦值操作會發生所有權的轉移,舊值變得不可用(內建型別除外)
    • 同一作用域內,不允許出現兩個可變引用,可以減少產生競態條件的情況
    • 不能在擁有不可變引用的同時擁有可變引用
  5. 切片 slice,在使用和內部實現上和 cpp 的 string_view 或者 golang 的切片都有點類似,對於函式的入參而言使用切片和原始資料型別無差異
  6. 結構體 struct 用法和 cpp 中類似
    • 方法定義等同於 cpp
    • 關聯函式的使用類似於 cpp 中的 static 函式使用
    • #[derive(Debug)] 列印整個結構和值

習題

華氏攝氏度溫度互相轉換

fn c_to_f(c: f32) -> f32 {
return 1.8 * c + 32.0;
} fn f_to_c(f: f32) -> f32 {
return (f - 32.0) / 1.8;
}

列印斐波那契n項值

fn fibonacci(x: u32) -> u32 {
if x == 0 {
return 0;
} else if x == 1 || x == 2 {
return 1;
} else {
return fibonacci(x - 1) + fibonacci(x - 2);
}
}

TODO

型別系統的轉換看起來還有點不相同,像其它的語言的型別轉換,在 rust 中會報錯

day2 (2021/05/28)

列舉

在 cpp 中的定義

enum Color { Read, Green };

在 rust 中這樣也可以工作的很好,裡面的所有元素都是相同型別的,在 c 中元素型別預設為 int, cpp 中賦予了更多的型別定義。

更進一步,rust 中允許每個元素 關聯不同的資料型別,如官方教程中的

enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}

但是既然定義為 enum,那表現的語義就應該是型別相同的,即使內部資料型別有差異,就可以這樣使用了

#[derive(Debug)]
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
} fn foo(m: Message) {
println!("{:?}", m)
} fn main() {
foo(Message::Quit);
foo(Message::Move { x: 1, y: 2 });
foo(Message::Write(String::from("test-enum")));
foo(Message::ChangeColor(1, 2, 3))
} // output:
// Quit
// Move { x: 1, y: 2 }
// Write("test-enum")
// ChangeColor(1, 2, 3)

將不同的資料型別聚集在一起來表示同一型別的語義,理論上來說是提高了抽象表達能力的;但看起來又和結構體又十分相似尤其在 cpp 中,一個父類派生出多個子類,然後可以用父類指標來表達到不同的子類上,但在 rust 這種實現用於了列舉。

Option

實現和 cpp 中的 type_traits 非常類似,在 cpp 中在編譯時通過型別來匹配對應的函式。

enum Option<T> {
None,
Some(T),
}

對於一個函式的返回而言,如果需要返回空則直接返回 None;若是需要返回一個 i32,則返回 Some(i32)。進而函式呼叫後對結果進行 is_none() 的判斷,如果非空,則可以使用 x.unwrap() 取出值。

再次表達和 cpp 中的 SFINAE 極其相似,看起來還有一些執行時消耗。

match

match 是一個表示式,每個分支相關聯的程式碼是一個表示式,而表示式的結果值將作為整個 match 表示式的返回值。

=> 後每個分支的返回值型別必須相同,對於列舉的型別系統而言完成了一個閉環。

前面看到列舉可以由不同型別甚至攜帶不同的引數組成,開始我對列舉進行判斷相同時的構想是這個樣子的:

fn foo(m: Message) {
if m == Message::Quit {}
if m == Message::Write(String::from("compare")) {}
}

當然,上面的程式碼是行不通的,但是從這樣子來看的話,型別判斷還算準確,帶引數的要引數才能匹配,

match 出現後,在上面相同的語義上繼續放大了威力。enum 帶了引數是吧,我match可以對你的引數進行解析,然後中間的過程你自己決定,對於引數而言,不一定要相等,我約等於也可以。

結合 matchenum 才算是真正發揮了設計的效果。

一個語法糖 if let 可以省略 match 表示式,match 中的分支跟在 if let 之後。

day3 (2021/05/30)

rust 包管

rust 的包管理系統都是圍繞著 cargo 來進行的。有幾個相關的概念.

  • 包(Packages): 原始檔和描述包的 Cargo.toml 清單檔案的集合。
  • Crates :一個模組的樹形結構,是一個庫或一個可執行程式,分別稱為lib crate 或 binary crate。
  • 模組(Modules)和 use: 允許你控制作用域和路徑的私有性。
  • 路徑(path):一個命名例如結構體、函式或模組等項的方式

上面的話是官方的解釋(The Book 和 The Cargo Book).

在 cpp 中,沒有啥包的概念,只有一個名稱空間的概念,粗略一看和 rust 中的方式差的遠,golang 和 rust 同屬現代語言,這裡用 golang 做比較會好理解一些。

  • 模組路徑都好理解,理解為檔案系統的路徑就行。一般而言模組的名稱就是檔案的名稱,golang 中模組名為目錄名。
  • ccrate 和 package 的關係為,package 可以包含至多一個 lib crate 和多個 binary crate。rust 的 crate 功能就和 golang 中的 package main 相似,可以有多個。

root 的概念

  • crate root, src/main|lib.rs 就是一個與包同名的 crate 根。通過將檔案放在 src/bin 目錄下,一個包可以擁有多個二進位制 crate.
  • package root, 包的 Cargo.toml 檔案所在目錄
  • workspace root, 工作區的 Cargo.toml 檔案坐在目錄

集合

vector

提供了巨集的操作 vec!,作用類似 cpp 中的初始化列表在 vector 中的使用。

由於語法限制了不能多個可變引用,所以就沒有辦法變出這種腦淤血的程式碼,這種程式碼在 cpp 中叫做迭代器失效,rust 直接禁止這樣的寫法

    let mut y = vec!["z", "x", "h"];
for v in &mut y {
println!("{}", v);
y.push("sb");
}

內部實現和各種 vector 實現無異,都是 data,len,capacity 的實現,增長因子為 2.

與 cpp 比起來,多了一個 pop 操作。

字串

rust 語言內建字串型別:str,字串 slice,它通常以被借用的形式出現,&str. 引用儲存在其它別處的UTF-8的資料,目前這些資料為字面量字串和String中的資料。

和 cpp 中的 const * char * 有點類似,但是提供了很多隻讀的操作,使用起來比較自然,並且是 unicode 而無需為操蛋的 char wchar 浪費精力。

fn read_str(s: &str) {}

read_str("string-literal");  // OK
let s = "&str"; read_str(s); // OK
let ss = String::from("String"); read_str(&ss); // OK, type coerced

對於需要使用到可變字串的操作而言,需要使用 String, 同樣是 unicode 編碼,底層實現為 Vec<u8>,不過使用向量不過是方便對資料的儲存,省了一遍造輪子的程式碼。

雖然是向量作為儲存,和 cpp 那種 std::string 表面為字串,實則為字元陣列的的實現不同,String 不支援隨機讀取,

let name = String::from("123舉個");
println!("len: {}", name.len()); // len: 13
println!("{:?}", name.as_bytes());
// [49, 50, 51, 228, 184, 190, 228, 184, 170, 240, 159, 140, 176]
// 1 --> 49
// 2 --> 50
// 3 --> 51
// 舉 --> 228, 184, 190
// 個 --> 190, 228, 170
// --> 240, 159, 140, 176

String 不支援隨機存取的操作,但可以使用 slice 來繞過編譯器的檢查,但是一個unicode碼可能是多個位元組組成的,當索引卡在一個字元中間時,直接 panic。

str 作為一個區分,String 有所有權,str 看起來並沒有所有權的一些規則限制,如同i32一般(不過本就是內建型別)

HashMap

map 用著並不自然,存取操作不能使用常規的 map[key] = value. 對於資料的獲取可以說非常反人類了,map[&7] 這種東西都出來了,設計可能合理但是使用不自然。

習題

給定一系列數字,使用 vector 並返回這個列表的平均數(mean, average)、中位數(排列陣列後位於中間的值)和眾數(mode,出現次數最多的值)。

// 平均數
fn mean(nums: &Vec<i32>) -> Option<i32> {
if nums.len() == 0 {
return None;
} let mut m: i32 = 0;
for n in nums.iter() {
m += n;
} return Some(m / nums.len() as i32);
} // 中位數
fn middle(nums: &Vec<i32>) -> Option<i32> {
if nums.len() == 0 {
return None;
} let mut tmp = nums.clone();
tmp.sort();
return Some(tmp[tmp.len() / 2]);
} // 眾數
fn mode(nums: &Vec<i32>) -> Option<i32> {
if nums.len() == 0 {
return None;
} let mut freq = HashMap::new();
for n in nums.iter() {
let count = freq.entry(n).or_insert(0);
*count += 1;
} let mut key = nums[0];
let mut max: i32 = 0;
for (n, count) in freq.iter() {
if max < *count {
max = *count;
key = **n;
}
} return Some(key);
}

day4 (2021/05/31)

panic! 中斷程式的執行,使用環境變數 RUST_BACKTRACE=1 可以列印退出程式時的堆疊呼叫情況。

錯誤錯誤的核心結構為 enum Result.

enum Result<T, E> {
Ok(T),
Err(E),
}

對於一個函式而言,無錯誤發生則返回 Ok(T), 發生錯誤則返回 Err(E).

傳播錯誤的簡寫:? 運算子,工作方式如同以下 match 的邏輯.

let _ = match foo {
Ok(file) => file,
Err(e) => return Err(e),
};

foo 為一個 Result,其值為錯誤時,直接作為整個函式的返回值返回。當前來看 ? 運算子只能作用於返回值型別為 Result<T, E> 的函式。

day5 (2021/06/02)

學習泛型

一個普通的 c++ 的泛型相加函式實現如下

template <typename T> T add(T lhs, T rhs) { return lhs + rhs; }
// 特化版本·
template <> int add(int lhs, int rhs) { return lhs * 2 + rhs * 2; }

在 rust 中的實現非常類似,但是這是編譯不過的版本

fn add<T>(x: T, y: T) -> T { x + y }

x + y 的過程是不確認的,如果是兩個自定義型別,可能是不支援相加的操作的,rust 同c++一樣編譯不過去。

和 c++ 不太一樣的是,c++ 是例項化的時候編譯錯誤的,rust 還沒有例項化的時候就提示不對,因為需要提前標註是否支援相加的操作.

形式如下,rust 中需要提前表明這個型別需要支援相加的操作然後在內部才能夠相加

fn add<T: Add<Output = T>>(x: T, y: T) -> T { x + y }

有一點類似 c++ 中的萃取技術,提前預判型別

template <typename T>
typename std::enable_if<std::is_integral<T>::value, T>::type add2(T lhs, T rhs) {
return lhs + rhs;
}

在使用上又有點類似於 interface,舉個 golang 的例子,不過 golang 是執行時,如果未實現了 add 方法會 panic(簡單的可以編譯報錯)

type Adder interface {
add(lhs, rhs int) int
} type FuckWork struct{}
func (f *FuckWork) add(lhs, rhs int) int { return lhs*2 + rhs*2 } func fuckAdder(adder interface{}) int {
ar := adder.(FuckWork)
return ar.add(1, 2)
} func main() {
fw := FuckWork{}
fmt.Println(fuckAdder(fw)) // 6
}

rust 的 trait 同時吸收了 c++ 的 type_traits 機制和一般語言的 interface 設計。

泛型的設計應該滿足於 trait 才能夠進行,上面的 add 就需要優先表示這個型別是支援 + 的操作。

trait 的定義是一種將方法簽名組合起來的方法,目的是定義一個實現某些目的所必需的行為的集合。這是官方的定義。

個人理解是為了泛型而存在的一種語言特性,在泛型中將相同的操作方法剝離出來,分別對應到不同型別實現上,是一種自底向上的實現;在使用上和 interface 極其類似,唯一的區別就是編譯時。

以下定義了一個評估的 trait,包含了兩個方法,其中一個方法預設實現。

pub trait Evaluate {
fn height(&self) -> usize;
fn weight(&self) -> usize { return 0; }
}

實現的方式和寫詩一般 imple xx for XX,為 Persion 這個結構實現了 height 的 trait

struct Person {
name: String,
} impl Evaluate for Person {
fn height(&self) -> usize {
self.name.len()
}
}

一個預設和特化實現的 trait 呼叫方式如下

let p = Person {name:String::from("panda")};
println!("persion {} weight {} and height {}", p.name, p.weight(), p.height());
// output:
// persion panda weight 0 and height 5

接著上面的 rust 泛型 相加的操作,實現了一個自定義的結構的泛型操作

use std::ops::Add;

#[derive(Debug)]
struct FuckWork<T> {
work_name: T,
work_type: T,
} // Notice that the implementation uses the associated type `Output`.
impl<T: Add<Output = T>> Add for FuckWork<T> {
type Output = Self; fn add(self, other: Self) -> Self::Output {
Self {
work_name: self.work_name + other.work_name,
work_type: self.work_type + other.work_type,
}
}
} fn main() {
let x = FuckWork { work_name: 1, work_type: 2 };
let y = FuckWork { work_name: 2, work_type: 4 }; let xf = FuckWork { work_name: 1.0, work_type: 2.0 };
let yf = FuckWork { work_name: 2.0, work_type: 4.0 }; println!("{:?}", x + y); // FuckWork { work_name: 3, work_type: 6 }
println!("{:?}", xf + yf); // FuckWork { work_name: 3.0, work_type: 6.0 }
}

但是以上部分並不是對所有的 T 的add 操作都支援的,比如 String,但由於並不允許進行修改,所以在新增一個的 fucker 來說明如何支援更多的相加型別操作。

struct Fucker {
name: String,
} impl Add for Fucker {
type Output = Self;
fn add(self, other: Self) -> Self::Output {
Self {
name: format!("{} say what the fuck? {}\n", self.name, other.name),
}
}
}

當有多個型別時,可以這樣對型別實現的 trait 進行限定

fn some_function<T, U>(t: T, u: U) -> i32
where T: Display + Clone,
U: Clone + Debug
{
}

生命週期

生命週期沒啥講的,c++裡面已經有非常多的實踐了。

rust 中的生命週期的檢查主要是為了避免垂懸引用的存在,並且在編譯器就需要知道引用的生命週期。

借用檢查器(borrow checker)可以對生命週期進行檢查,當其判斷不了生命週期的長短時,需要使用生命週期註解 ' 來表明生命週期 'a 'b

註解了相同的生命週期則表明變數的生命週期長度是相同的。largest 中的入參和返回的生命週期都是相同的。

fn main() {
let foo = String::from("first");
let goo; {
let tmp = String::from("tmp");
goo = largest(&foo, &tmp);
println!("{:?}", goo);
} println!("{}", goo);
} fn largest<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() > b.len() {
a
} else {
b
}
}

程式碼是編譯不過去的,在函式中表明生命週期是相同的,函式返回後就回到了外層的作用域比較了,很明顯當返回的是 tmp 的引用時,會造成垂懸引用,所以編譯器就報錯了。

error[E0597]: `tmp` does not live long enough
--> src/main.rs:7:29
|
7 | goo = largest(&foo, &tmp);
| ^^^^ borrowed value does not live long enough
8 | println!("{:?}", goo);
9 | }
| - `tmp` dropped here while still borrowed
10 |
11 | println!("{}", goo);
| --- borrow later used here

結構體中的生命週期

含引用的結構體,不過這需要為結構體定義中的每一個引用新增生命週期註解,這是強制性的操作。因為在函式或者其它方法處引用可能與結構體欄位中的引用相關聯。

Fucker 中的兩個引用的生命週期我們註解是相同的,在進入函式是,入參結構體的引用生命週期註解和返回是一樣的,進而說明內部的引用生命週期和返回也是一樣的,編譯器就知道了這個函式的返回生命週期。

struct Fucker<'a> {
part_a: &'a str,
part_b: &'a str,
} fn largest<'a>(f: &'a Fucker) -> &'a str {
if f.part_a.len() > f.part_b.len() {
f.part_a
} else {
f.part_b
}
}

生命週期註解省略

函式或方法的引數的生命週期被稱為 輸入生命週期(input lifetimes),而返回值的生命週期被稱為 輸出生命週期(output lifetimes)。

三條可以省略生命週期註解的規則

  1. 每一個是引用的引數都有它自己的生命週期引數。換句話說就是,有一個引用引數的函式有一個生命週期引數:fn foo<'a>(x: &'a i32),有兩個引用引數的函式有兩個不同的生命週期引數,fn foo<'a, 'b>(x: &'a i32, y: &'b i32),依此類推。
  2. 如果只有一個輸入生命週期引數,那麼它被賦予所有輸出生命週期引數:fn foo<'a>(x: &'a i32) -> &'a i32
  3. 如果方法有多個輸入生命週期引數並且其中一個引數是 &self&mut self,說明是個物件的方法(method), 那麼所有輸出生命週期引數被賦予 self 的生命週期。

三條規則是自上向下(1->3)應用的策略,是一個規則逐個適配的過程。

day6 (2021/06/06)

測試部分,略過,用過 gtest 感覺區別不大

閉包

用法上 rust 和 c++ 都不好用,不夠自然,golang 的用起來最直接舒服。

普通用法和 c++ 一樣,差異在於對於變數捕獲上,c++的值捕獲呼叫拷貝建構函式,引用捕獲將物件的引用傳遞給匿名函式。

rust 中的所有權機制,導致出現了幾種 trait

  • FnOnce 消費從周圍作用域捕獲的變數,閉包周圍的作用域被稱為其 環境,environment。為了消費捕獲到的變數,閉包必須獲取其所有權並在定義閉包時將其移動進閉包。其名稱的 Once 部分代表了閉包不能多次獲取相同變數的所有權的事實,所以它只能被呼叫一次。
  • FnMut 獲取可變的借用值所以可以改變其環境
  • Fn 從其環境獲取不可變的借用值

使用起來可以說相當腦殘了,強行和 trait 繫結在一起,感受一下

let mut ms = String::from("test");
let change_str = || ms = String::from("sdf");
change_str(); // 編譯失敗 cannot borrow as mutable // 修改後的程式碼
let mut ms = String::from("test");
let change_str = || ms = String::from("sdf");
fu_mut(change_str);
fn fu_mut<T>(mut f: T)
where
T: FnMut(), // 需要說明這個閉包可以修改
{
f();
}

必要需要實現的這麼複雜的話,不如直接使用一般函式;目前唯一想到的是在寫庫的時候,在庫實現中定義好 FnMut,呼叫者使用短的閉包就可以了。

迭代器

資料結構實現,唯一的方法就是 next,簡單看了相關結構,和 c++ 中無異。

pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
} pub struct Iter<'a, T: 'a> {
ptr: NonNull<T>,
end: *const T,
_marker: PhantomData<&'a T>,
}

還是產生其它迭代器的用法簡直花裡胡哨的,十足的語法糖,不過也可能增加一些心智負擔。另外一些比較效能的地方可以對比 llvm ir 的實現了。

cargo

cargo 可以根據程式碼中的文件註釋來生成對應的文件,格式為 markdown,這個真不錯,對比 doxygen 簡單很多用起來。

day7 (2021/06/07)

enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}

官方描述對於編譯器而言,Quit 不需要分配記憶體。我猜和 c++ 中的 struct {} 一樣,需要佔用一個位元組的記憶體;對於整個 enum 來說,記憶體佔用的計算和 c++ 中的 union 相同。

細節部分需要通過 llvm ir 得到。

enum List {
Cons(i32, List),
Nil,
}

這是一個關於連結串列部分的錯誤定義,Cons 是一個遞迴的寫法,於是編譯器就無法得到具體所佔空間大小了。在 c++ 中同樣編譯不過,需要將 List 變成 *List,rust 中是 Box<T>.

智慧指標

std::boxed::Box 官方的描述是 A pointer type for heap allocation. 結合 rust 的不可變借用規則,和 c++ 中的 std::unique<T> 一毛一樣。

clone 呼叫是將值複製的一個新的智慧指標物件。

解引用

rust 中的解引用和 c++ 也可以用相同來形容,預設只解 & 的引用,但是 c++ 中是提供過載操作 operator *;rust 中還是 trait,這一點很統一。

MyBox 型別的解引用符需要實現 trait Deref,告知編譯器這個型別可以進行 * 操作,由於是庫實現,本質還是解 &,因為 deref 返回型別還是引用。

struct MyBox<T> {
value: T,
} impl<T> MyBox<T> {
fn new(x: T) -> MyBox<T> {
MyBox { value: x }
}
} impl<T> Deref for MyBox<T> {
type Target = T; fn deref(&self) -> &Self::Target {
&self.value
}
}

解引用強制多型

解引用衍生的一個概念,看著像型別推導,但是會呼叫 deref 還是更像是執行時多型。

fn hello(name: &str) {
println!("Hello, {}!", name);
} fn main() {
let m = MyBox::new(String::from("Rust"));
hello(&m);
}

m 解引用後是 &String 型別,hello 的入參是 &str,但是 String 實現了 Deref trait

impl ops::Deref for String {
type Target = str; #[inline]
fn deref(&self) -> &str {
unsafe { str::from_utf8_unchecked(&self.vec) }
}
}

返回型別為 &str,在進入函式之前,先呼叫 deref.

強制多型的規則

Rust 在發現型別和 trait 實現滿足三種情況時會進行解引用強制多型:

  • 當 T: Deref<Target=U> 時從 &T 到 &U。
  • 當 T: DerefMut<Target=U> 時從 &mut T 到 &mut U。
  • 當 T: Deref<Target=U> 時從 &mut T 到 &U。

drop trait

rust 中的解構函式也實現為一種 trait,以此支援 RAII,定義如下

pub trait Drop {
pub fn drop(&mut self);
}

對於希望主動析構 rust 還提構了一個 std::mem::drop(),目前也是看不到有什麼用處,都析構了不如直接放在一個臨時作用域內。

Rc 引用計數智慧指標

翻版 std::shared_ptr<T>,clone 呼叫並不是真正的呼叫,而是計數加1

為避免引用迴圈,又引入了 Weak,熟悉的套路。

std::cell::RefCell

官方的文件說明是 執行時檢查借用規則的可變記憶體區域.

RefCell<T> 記錄當前有多少個活動的 Ref<T>RefMut<T> 智慧指標。每次呼叫 borrow,RefCell<T> 將活動的不可變借用計數加一。當 Ref<T> 值離開作用域時,不可變借用計數減一。

核心為將編譯時的規則用在執行時,RefCell<T> 在任何時候只允許有多個不可變借用或一個可變借用

let c = RefCell::new(5);
let b = c.borrow(); // ok
let bb = c.borrow(); // ok
let m = c.borrow_mut(); // panic

結合 Rc<T>RefCell<T> 來擁有多個可變資料所有者,語義和核心的 rcu 驚人的相似,但是語法更為限制。

enum List {
Cons(i32, RefCell<Rc<List>>),
Nil,
}

併發

雖然很多語言覺得增加執行時來換取更多功能沒有什麼問題,但是 Rust 需要做到幾乎沒有執行時,同時為了保持高效能必須能夠呼叫 C 語言,這點也是不能妥協的

這一點上面,雖然golang可以儘可能少的依賴c相關的東西,但是有些場景根本逃不掉 cgo=1,比如對網路流量進行獲取,一般而言都是基於 libpcap 庫進行開發的,這個時候cgo還是必須開啟。

Rust 標準庫只提供了 1:1 執行緒模型實現,這樣的執行時相對較小。

rust 使用 spawn 建立執行緒,join 等待子執行緒的結束。為減少資源的競爭,可以使用 move 關鍵字將所有權轉交至新執行緒中。

use std::thread;

fn main() {
let v = vec![1, 2, 3]; let handle = thread::spawn(move || {
println!("Here's a vector: {:?}", v);
}); handle.join().unwrap();
}

rust 中的訊息傳遞部分借鑑的 golang,通過佇列 channel 來傳遞訊息的。在 rust 中,生產或者消費佇列中的元素都會發生所有權的轉變,使用 Rc<T> 可以存在多所有權,但是看起來管理也是大麻煩。

多生產者單消費者 channel 實現執行緒間的訊息同步。

use std::thread;
use std::sync::mpsc;
use std::time::Duration; fn main() {
let (tx, rx) = mpsc::channel(); thread::spawn(move || {
let vals = vec![1, 2, 3];
for val in vals {
tx.send(val).unwrap();
thread::sleep(Duration::from_secs(1));
}
}); for received in rx {
println!("Got: {}", received);
}
}

使用互斥變數來同步也是一種方案,使用是將 mutex 和 value 繫結在一起的,不太像 c++ 中的 std::loca_guard<std::mutex> 的用法。

let mutex = Mutex::new(1);
let mut x = mutex.lock().unwrap();
*x = 2;

rust 的面向物件

像 c++ 中的那種繼承派生在 rust 中是不存在的,對於多型而言更像是 golang 中的 interface,rust 的 trait 機制可以實現類似的多型功能。

day8 (2021/06/08)

unsafe 塊用來顯式宣告不安全的程式碼,總體而言還是很自然的,像寫 c 一樣的風格

  1. union、static 這種強規則限制
  2. 繞過語言的一些限制,比如同時同時存在兩個可變引用,這種在 c 裡面是再正常不過的語法
  3. 和 c 互動 ffi
  4. 由上引出的 trait 等一系列複合特性

關聯型別就是別名,類似 c++ 中的泛型實現時會定義一個通用的型別來使用,

typedef _CharT    char_type;

Self 關鍵字為實現 trait 的型別的類型別名,

超trait(supertrait) 為 triat 的依賴宣告,OutlinePrint trait 表明只能用於已經實現過 fmt::Display 的型別才能夠使用。

trait OutlinePrint: fmt::Display {
fn outline_print(&self) {
let output = self.to_string();
// snip...
}
}

返回閉包需要用只能指標包裹一層,因為編譯器無法知道具體的所佔空間大小。

返回函式指標或者函式作為入參,都是直接使用函式簽名作為型別。