Skip to content

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:能夠自動推導,如 SendSyncUnpinPanicUnwind
  • Derivable Traits:編譯器提供簡單的標籤 #[derive(...)] 提供快速實作,如 DebugClone/CopyPartialEq/EqDefault
  • Marker Traits:不需要做任何實作,單純只是編譯器用來貼標籤的,如 SizedCopy

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。

unsafe impl Send for MyType {}
unsafe impl Sync for MyType {}

Derivable Traits

Copy & Clone

實現 Copy 的類型的一定也是 Clone 的類型,Clone 是 Supertrait

Copy 的特性:

  1. 只會完全複製 stack 的資料,不管 heap
  2. 實作包在 Rust 內部,開發者無法修改
  3. 觸發的時間點在幾個地方觸發:給值、參數傳入、回傳結果
  4. 類型中的所有成員都必須是 Copy、並且沒有實現 Drop,該類型才能是 Copy

Clone 的特性:

  1. 複製 stack / heap 的資料都可以
  2. 開發者必須要實作 Clone::clone(&self)
  3. 觸發複製的時間點只有呼叫 Clone::clone(&self) 的時候
  4. 類型沒有特別限制