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...
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: