Rust Trist
Trait
Trait 是什麼東西
Trait 中定義了一組方法,類似於其他語言當中的接口,但是不同於其他語言中的接口(後面會展開)。因爲其內部是一組方法,因此 Trait 所定義的是一種公共行爲。比如所有男生被要求上公廁的時候只能進男廁所,所有運動員被要求不允許毆打裁判……也就是讓一些不同的類型卻擁有相同的行爲(可調用方法)。
自定義 Trait
你可以通過如下的方式自定義一個 Trait,這個 Trait 中擁有一個方法簽名(只有簽名,沒有實現),他的意思是要求具備 SayHello
這個 Trait 的類型都必須擁有一個方法,這個方法名叫 say
,並且這個方法返回一個 str
。
trait SayHello {
fn say(&self) -> &'static str;
}
PS:&self
參數是必要的,不然實例化後的對象無法調用。
爲類型實現 Trait
如果你希望爲自己的類型實現 SayHello
這個 Trait 你可以像這樣
struct Man {
name: String,
}
impl SayHello for Man {
fn say(&self) -> &'static str {
"Hello Man"
}
}
fn main() {
let m: Man = Man{name: String::from("Ant")};
println!("{}",m.say());
}
默認實現
如果你定義來一百個類型,而每一個類型都需要實現 SayHello
Trait,但是每一個 say
方法返回的內容都是一模一样的,因此你可以爲 Trait 添加默認實現。
trait SayHello {
fn say(&self) -> &'static str {
"Hello 🌍"
}
}
struct Man {
name: String,
}
impl SayHello for Man {}
fn main() {
let m: Man = Man{name: String::from("Ant")};
println!("{}",m.say());
}
原本 Trait 當中只是添加了方法簽名,並沒添加實現,所以某一個類型需要實現某個 Trait 的時候需要實現裏面的方法。但是,當添加了默認實現之後,再爲類型實現 Trait 的時候並不需要實現裏面的方法,而且只需要直接使用即可(這時候依舊可以去手動實現,並且手動實現會復蓋原本的實現)。
使用 Trait 作爲參數
實例
Trait 既然定義了某些類型的公共行爲,除了讓類型去實現 Trait 之後主動調用之外,還可以讓第三方函數來調用這個實現,比如下面這樣。
trait SayHello {
fn say(&self) -> &'static str {
"Hello 🌍"
}
}
struct Man {
name: String,
}
impl SayHello for Man {}
fn say(object: impl SayHello) {
println!("say \"{}\" in fn say",object.say())
}
fn main() {
let m: Man = Man{name: String::from("Ant")};
say(m)
}
此處把 Trait 作爲參數,用來約束形參 object 的類型,但是與其他如 i32,i64 不同的是 Trait 多了一個 iml
關鍵字,這個意思是說,object 參數是一個實現了 SayHello
Trait 的實例。如果你需要以借用的方式傳遞參數,那麼 &
關鍵字需要添加在 impl
之前,:
之後。
其實只是語法糖
另外上面這種寫法其實只是一種語法糖,是完全等價與下面的這種寫法的。不難看出,與其說是使用 Trait 作爲參數,不如說是一個泛型方法更便於理解。
fn say<T: SayHello> (object: &T) {
println!("say \"{}\" in fn say",object.say())
}
上面的語法糖也被叫做 Trait 約束(Trait Bound)。那麼什麼使用使用那一種書寫方式呢?一般來說對於比較簡單的示例會使用第一種寫法(帶有 impl 關鍵字),而對於比較複雜的情況使用完整寫法(Trait Bound 使用泛型)。如下面的例子。
fn func(item1: impl SayHello, item2: impl SayHello) {}
func 接受的兩個參數確實擁有相同的 Trait 但是並不能確定他們是不是相同的類型,如果你需要保證這一點則需要通過完整的寫法:
fn func<T: SayHello>(item1: T, item2: T) {}
而且不難看出,這時候完整的寫法看起來反而更加簡短。而且由於 Trait 可以通過 + 進行連接——一個類型可以擁有多個 Trait,函數自然也能要求某個參數同時實現了多個 Trait,因此可以想像如果通過 + 連接了多個 Trait 的時候第一種寫法並不簡短,也並不優雅,所以這時候可以通過完整寫法+ where
來讓代碼看起來更加優雅一些
where 語法
相當於把 Trait 分開指定,讓代碼在可讀性上得到提升。
fn func<T, U>(item1: T, item2: U)
where
T: Display + SayHello,
U: SayHello + PartialOrd,
{
// TODO
}
返回實現了 Trait 的類型
既然 Trait 可以被當作參數類型一樣使用,那麼時候可以作爲返回值類型使用呢?結論是可以的
trait SayHello {
fn say(&self) -> &'static str {
"Hello 🌍"
}
}
struct Man {}
impl SayHello for Man {}
fn new_sayer()-> impl SayHello {
Man{}
}
fn main() {
let m = new_sayer();
println!("{}",m.say());
}
如此一來我們便可以在 new_sayer
當中返回任何實現了 SayHello
的類型。但是這裏有一個問題,那就是當你觀察 main
函數當中的變量 m 的數據類型的時候你會發現他並不是 Man 類型,而只是一個實現了 SayHello 的類型。關於這一點我們會在後續關於迭代器和閉包的相關內容中再次提到。