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

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 协议》,转载必须注明作者和本文链接
令狐一冲
文章
255
粉丝
121
喜欢
308
收藏
128
排名:327
访问:2.9 万
私信
所有博文
社区赞助商