Rust trait
trait 也就是屬性,在 Rust 中可以當成是用來修飾 struct 的形容詞,某種程度上可以當成是物件導向的 interface 的概念,代表這個 struct 實作了某些 API,其他人可以呼叫。
一個定義的 trait 通常下方會有多個 fn 需要由 struct 去實作。 我們用下面最簡單的範例來說明:
// 定義一個說話的 trait
trait Speak {
fn say(&self) -> String;
}
// 隨便定義兩個 struct,分別是狗和人
struct Dog;
struct Human;
// 為 Dog 實作 Speak,代表狗會說話
impl Speak for Dog {
fn say(&self) -> String {
"汪汪".to_string()
}
}
// 為 Human 實作 Speak,人也會說話
impl Speak for Human {
fn say(&self) -> String {
"你好".to_string()
}
}
fn main() {
let dog = Dog;
let person = Human;
// 我們可以直接讓這兩個 struct 說話
println!("狗說:{}", dog.say());
println!("人說:{}", person.say());
}
Orphan Rule
當要為某個 type 實作 trait 時,要確保該 type 和 trait,至少有一個是在當前 crate 所定義,也就是不能為第三方的 type 定義第三方的 trait。
impl Trait vs impl dyn Trait
Rust:impl Trait vs impl dyn Trait
Into & From
Into & From 兩者是兄弟,當定義 From<T> for U 也就隱含著 Into<U> for T,可以從 T 轉換成 U。
但是反過來並不成立,實作 Into 不會有 From 的功能,所以大部分我們都會選擇實作 From,除非在舊版要繞過 Orphan Rule,可參考官方文件。
- From
use std::convert::From;
struct Value {
v: i32,
}
impl From<i32> for Value {
fn from(i: i32) -> Self {
Value { v: i }
}
}
fn main() {
let num1 = Value::from(30);
let num2: Value = 40.into();
println!("Result: {:} {:}", num1.v, num2.v);
}
- Into
use std::convert::Into;
struct Value {
v: i32,
}
impl Into<Value> for i32 {
fn into(self) -> Value {
Value { v: self }
}
}
fn main() {
//let num1 = Value::from(30_i32); // fail
let num2: Value = 40_i32.into();
}
TryInto & TryFrom
Into 和 From 並不允許轉換失敗,如果要支援轉換失敗,可用 TryInto 和 TryFrom。可參考 TryFrom and TryInto
- TryFrom
use std::convert::TryFrom;
#[derive(Debug, PartialEq)]
struct PositiveNumber(i32);
impl TryFrom<i32> for PositiveNumber {
type Error = ();
fn try_from(v: i32) -> Result<Self, ()> {
if v > 0 {
Ok(PositiveNumber(v))
} else {
Err(())
}
}
}
fn main() {
// TryFrom
assert_eq!(PositiveNumber::try_from(8), Ok(PositiveNumber(8)));
assert_eq!(PositiveNumber::try_from(-5), Err(()));
// TryInto
let result: Result<PositiveNumber, ()> = 8_i32.try_into();
assert_eq!(result, Ok(PositiveNumber(8)));
let result: Result<PositiveNumber, ()> = (-5_i32).try_into();
assert_eq!(result, Err(()));
}
- TryInto
use std::convert::TryInto;
#[derive(Debug, PartialEq)]
struct PositiveNumber(i32);
impl TryInto<PositiveNumber> for i32 {
type Error = ();
fn try_into(self) -> Result<PositiveNumber, ()> {
if self > 0 {
Ok(PositiveNumber(self))
} else {
Err(())
}
}
}
fn main() {
// TryFrom - compile fail
//assert_eq!(PositiveNumber::try_from(8), Ok(PositiveNumber(8)));
//assert_eq!(PositiveNumber::try_from(-5), Err(()));
// TryInto
let result: Result<PositiveNumber, ()> = 8_i32.try_into();
assert_eq!(result, Ok(PositiveNumber(8)));
let result: Result<PositiveNumber, ()> = (-5_i32).try_into();
assert_eq!(result, Err(()));
}
Rust 內建的 trait
主要分為三種:
- Auto Traits:能夠自動推導,如
Send、Sync、Unpin、PanicUnwind - Derivable Traits:編譯器提供簡單的標籤
#[derive(...)]提供快速實作,如Debug、Clone/Copy、PartialEq/Eq、Default - Marker Traits:不需要做任何實作,單純只是編譯器用來貼標籤的,如
Sized、Copy
auto trait
一般的 trait 都需要我們自己去 impl Trait for MyStruct,但是 Rust 有些內建 trait 會根據 struct 來自動推導。
只要該結構體內部所有成員都實作某個 auto trait,那這個結構體就自動實作該 trait,相反地如果有一個成員沒有實作,那這個結構體就不會實作。
Send & Sync
- Send:允許變數的 ownership 從某一個 thread 轉移到另一個 thread,兩個 thread 不會同時存取。
例如 thread::spawn 的 closure 使用 move 時,就要確保該變數有 Send。
use std::thread;
let v = vec![1, 2, 3];
// 因為 vec![] 有 Send,所以可以傳到別的 thread
thread::spawn(move || {
println!("{:?}", v);
});
幾乎所有 Rust 變數都是 Send,但是有些例外,例如 Rc<T> 只能在 single thread 使用,需要 Arc<T> 才可以。
- Sync:Sync 允許多個 thread 在同個時間使用某個變數
官方的定義是如果 &T 是 Send,T 就是 Sync,也就是實現 Sync 的變數可以確保在多個 thread 中能擁有該變數的引用。
值得注意的是這邊實現 Sync 只能讀取該變數,如果是要能修改,那就需要使用 atomic 或是 Mutex<T>/RwLock<T> 才行。
- 兩者比較
會有 Send 和 Sync 最主要是在 compile-time 防止 data race。下面是兩者在各個 smart pointer 的比較。
| 型別 | Send | Sync |
|---|---|---|
Rc<T> |
X | X |
Arc<T> |
O | O (但是 T 需要是 Send+Sync) |
RefCell<T> |
O | X |
Mutex<T>/RwLock<T> |
O | O |
如果我們真的確認某個 struct 沒有 data race,那也可以自行用 unsafe trait 加上 Send 和 Sync。
Derivable Traits
Copy & Clone
實現 Copy 的類型的一定也是 Clone 的類型,Clone 是 Supertrait
Copy 的特性:
- 只會完全複製 stack 的資料,不管 heap
- 實作包在 Rust 內部,開發者無法修改
- 觸發的時間點在幾個地方觸發:給值、參數傳入、回傳結果
- 類型中的所有成員都必須是 Copy、並且沒有實現 Drop,該類型才能是 Copy
Clone 的特性:
- 複製 stack / heap 的資料都可以
- 開發者必須要實作
Clone::clone(&self) - 觸發複製的時間點只有呼叫
Clone::clone(&self)的時候 - 類型沒有特別限制