substrate轻松学系列5:编写pallet的Rust前置知识

Rust
329
0
0
2022-11-10

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时,涉及到的一些类型能有很好的理解。