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 協議》,轉載必須註明作者和本文連結