substrate輕鬆學系列5:編寫pallet的Rust前置知識

linghuyichong發表於2022-07-24

1 rust中的trait學習

在substrate的開發中,或者說pallet的開發中,trait的使用是非常常見的,所以理解Rust中的trait非常重要。本節不會從頭介紹trait的各種知識,如果你對Rust中的trait還不太瞭解,建議先學習trait基礎知識後,再來學習本教程接下來的內容。接下來的內容都是假定你已經對trait有了基本的瞭解。

1.1 trait的孤兒規則

Rust中的trait在使用上和其它程式語言中的介面類似,為一個型別實現某個trait就類似於在其它程式語言中為某個型別實現對應的介面。但是在使用trait的時候,有一條 非常重要的原則(為什麼重要?因為你如果不知道話,那麼在某些時候會發現哪都應該ok,但是就是編譯不過),那就是:
如果你想要為型別A實現trait T,那麼A或者T至少有一個是在當前作用域中定義的

舉兩個例子。

  • 正確的例子:

    //例子1
    pub trait MyTrait {
      fn print();
    }
    pub struct MyType;
    impl MyTrait for MyType {
      fn print() {
          println!("This is ok.");
      }
    }

    上面的例子1能夠正確的編譯,因為它遵循孤兒規則。

  • 錯誤的例子:

    //例子2
    use std::fmt::{Error, Formatter};
    impl std::fmt::Debug for () {
      fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
          Ok(())
      }
    }

    例子2無法編譯透過,因為不管是trait Debug的定義還是型別()的定義都是外部的,所以無法在我們的程式碼中為型別()實現trait Debug。

1.2 trait物件

Rust中不直接將trait當作資料型別使用,但是可以將實現了某個trait的具體的型別當作trait物件使用。看以下例子:

trait Drive{
    fn drive(&self);
}

struct Truck;

impl Drive for Truck {
    fn drive(&self) {
        println!("Truck run!");
    }
}


struct MotorCycle;

impl Drive for MotorCycle {
    fn drive(&self) {
        println!("MotorCycle run!");
    }
}

fn use_transportation(t: Box<dyn Drive>) {
   t.drive(); 
}

fn main() {
    let truck = Truck;
    use_transportation(Box::new(truck));

    let moto = MotorCycle;
    use_transportation(Box::new(moto));
}

在上面的例子中,use_transportation的引數就是一個trait物件,不關注具體的型別,只需要它具備drive能力即可。

1.3 trait的繼承

Rust只支援Trait之間的繼承,比如Trait A繼承Trait B,語法為:

trait B{}
trait A: B{}

Trait A繼承Trait B後,當某個型別C想要實現Trait A時,還必須要同時也去實現trait B。

1.4 關聯型別

關聯型別是在trait定義的語句塊中,申明一個自定義型別,這樣就可以在trait的方法簽名中使用該型別。如下:

pub trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
}

當為某個型別實現具有關聯型別的trait時,需要指定關聯型別為具體的型別,就像下面這樣:

struct Counter(u32);
impl Iterator for Counter {
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        // --snip--
    }
}

fn main() {
    let c = Counter(1);
    c.next();
}

2 一個例子

下面我們來看一個Rust的例子:

trait SystemConfig {
    fn system_configure(&self) {
        println!("system configure.");
    }
}

trait Config: SystemConfig {
    type Event: ToString;
    type Balance: ToString;
    type Currency: ToString;

    fn configure_event(&self, event: Self::Event);
    fn configure_balance(&self, balance: Self::Balance);
    fn configure_currency(&self, currency: Self::Currency);
}

struct Pallet {
    event: u64,
    balance: String,
    currency: String,
}

impl SystemConfig for Pallet {}

impl Config for Pallet {
    type Event = u64;
    type Balance = String;
    type Currency = String;
    fn configure_event(&self, event: Self::Event) {
        println!("configure, event is: {:?}", event);
    }
    fn configure_balance(&self, balance: Self::Balance) {
        println!("configure, balance is: {:?}", balance);
    }
    fn configure_currency(&self, currency: Self::Currency) {
        println!("configure, currency is: {:?}", currency);
    }
}

impl Pallet {
    fn new(event: u64, balance: String, currency: String) -> Self {
        Pallet {event, balance, currency}
    }

    fn init(&self) {
        self.configure_event(self.event);
        self.configure_balance(self.balance.clone());
        self.configure_currency(self.currency.clone());
    }
}

fn main() {
    let my_pallet = Pallet::new(1, "my balance".to_string(), "my currency".to_string());
    my_pallet.init();
}

上述程式碼中,我們定義了Config trait,然後為Pallet實現了相應的trait,最後在main函式中使用了它。

為什麼要寫這個例子?因為我覺得這個例子對後續我們寫pallet時,涉及到的一些型別能有很好的理解。

另外為了更好的理解後續的課程,可以讀一讀洋芋寫的文章深入理解substrate runtime

3 參考文件

course.rs/basic/trait/trait.html

rust-book.junmajinlong.com/ch11/03...

zhuanlan.zhihu.com/p/79539782

本作品採用《CC 協議》,轉載必須註明作者和本文連結
令狐一衝

相關文章