這是rust特質(trait)中頗有意思的一個特性。
一、前言
這個特性,在前面已經有接觸過,例如書本的第十三章節的迭代器特質就有這個例子:
impl Iterator for BooksIterator {
type Item = String;
fn next(&mut self) -> Option<String> {
if self.current_index < 3 {
let item = self.strs[self.current_index].clone();
self.current_index += 1;
Some(item)
} else {
None
}
}
}
初看這個代碼,頗為迷惑,為什麼要這麼搞,難道用通用類型不好嗎?
現在知道了,這個是rust特質的關聯類型(associated type)。
很自然地有個想法,關聯類型有什麼用?為什麼不能使用通用類型?
根據書本的示例,我自行體驗了一番,總結出一點:
由於rust版本的緣故,使用關聯類型更加靈活,或者説源於rust目前版本通用類型的侷限性,rust的關聯類型是通用類型的重要補充。
二、rust為什麼要使用關聯類型?
大體上可以這麼説,這是rust此類語言的侷限性所導致的。此類語言主張廢棄繼承,認為繼承的壞處多於好處(當然應該是和它們的設計目標有關),比較典型的有rust和go。
然而廢棄對象的繼承也有很多的副作用,主要是工程上的,非性能和安全上的。
現在開發業務系統的後台語言java中,因為有了繼承,它的通用類型就能夠以不同於rust的方式進行使用。
舉個例子:
public abstract class ExpParser<T extends ParamData> {
}
public class SpelParser extends ExpParser<SpelExp>{
}
public interface Study<T>{
public void learn(T t);
public void gather(T t);
}
public class Student implements Study<T>{
public void lean(T t){
if (t instanceof ChinaStudent){
}
else if (t instanceof AmericaStudent){
}
else{
}
}
}
這裏,Java就可以方便地限定T的類型,T是ParamData的子類。此外,即使不限定T的類型,也可以簡單地通過 instanceof 語法來操作。
但在rust中無法這麼進行限定(通過繼承指定範圍),所以rust中如果要限定對象類型範圍,那麼就通過where語句或者關聯類型來進行。
由於rust的特性,它基本上需要在編譯的時候需要知道實現類或者方法的具體類型,所以某種程度上,不如具有繼承特性的語言來得方便。
但rust這種關聯特性也有個好處:可以在實現類/方法中指定關聯類型的具體類型,從而增強了靈活性。
所以,rust通過關聯類型主要解決1個問題:
可以消除通用類型的侷限性。rust的通用類型在方法中必須羅列各種可能的類型,如果方法有許多實現,這就非常不方便了,但是用了關聯對象就可以避免,具體是在實現對象中列出
這種方式,看起來有點像設計模式的工廠模式。
例如有以下一個比較奇怪的特質:
trait Danger{
fn happen(&self,t:T)
where T:Display+Clone+Go+Run+Stop+Walk+Fly;
}
這個是不是很古怪?
但是用了關聯類型,where語句中的內容就可以分散到具體實現對象中。
下文的例子可以説明這個問題。
定義和使用關聯類型
1.在特質內使用type關鍵字定義個關聯類型(佔位符)
2.在具體的實現方法中,把佔位符替換為實際類型
例如:
trait Fight {
type Item;
fn attack(&self, other: &Self::Item);
fn defend<T>(&self, danger: &T)
where T: Danger;
}
impl Fight for Person {
type Item = Animal;
//其餘略
}
在特質Fight中Item稱為關聯類型佔位符,在具體的結構體Person中,不許把佔位符替換為具體的類型(這裏是Animal)。
就是這麼簡單!
三、示例
trait Danger {
fn happen(&self)->String;
}
struct Fire{
address: String
}
impl Danger for Fire{
fn happen(&self)->String {
//返回address+"燃燒了"
self.address.clone()+"燃燒了"
}
}
trait Fight {
type Item;
fn attack(&self, other: &Self::Item);
fn defend<T>(&self, danger: &T)
where T: Danger;
}
#[derive(Debug)]
struct Person {
name: String,
age: u32,
sex: String,
}
#[derive(Debug)]
struct Animal {
name: String,
age: u32,
}
impl Fight for Person {
type Item = Animal;
fn attack(&self, other: &Self::Item) {
println!(
"{}歲{}(性別:{}) 攻擊了 {}歲{}",
self.age,
self.name,
self.sex,
other.age,
other.name
);
}
fn defend<T: Danger>(&self, danger: &T) {
println!("{},{}歲{}(性別:{}) 奮起併力圖戰勝它們",danger.happen(), self.age, self.name, self.sex);
}
}
impl Fight for Animal {
type Item = Person;
fn attack(&self, other: &Self::Item) {
println!("{}歲{} 攻擊了 {}歲{}", self.age, self.name, other.age, other.name);
}
fn defend<T: Danger>(&self, danger: &T) {
println!("{},{}歲{} 跑了", danger.happen(),self.age, self.name);
}
}
fn draw_fire() {
println!(" ( . )");
println!(" ) ( )");
println!(" . ' . ' . ' .");
println!(" ( , ) (. ) ( ', )");
println!(" .' ) ( . ) , ( , ) ( .");
println!(" ). , ( . ( ) ( , ') .' ( , )");
println!(" (_,) . ), ) _) _,') (, ) '. ) ,. (' )");
println!("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^");
}
fn draw_girl_face() {
println!(" _____ ");
println!(" __/ \\__ ");
println!(" _/ (o) (o) \\_ ");
println!(" / < \\ ");
println!(" | \\___/ | ");
println!(" | _________ | ");
println!(" \\_/ \\__/ ");
println!(" | /\\ /\\ | ");
println!(" | | | | | ");
println!(" | | | | | ");
println!(" | \\/ \\/ | ");
println!(" \\ _/ ");
println!(" \\______/ ");
println!(" | | ");
println!(" | | ");
println!(" |__| ");
}
fn main() {
println!("\n火焰出現了:");
draw_fire();
println!("\n沐拉出現了:");
draw_girl_face();
let lu = Person { name: "沐拉".to_string(), age: 13, sex: "女".to_string() };
let dog = Animal { name: "小狗".to_string(), age: 3 };
let fire = Fire{address: "森林".to_string()};
println!("{:?}", lu);
println!("{:?}", dog);
lu.attack(&dog);
dog.attack(&lu);
lu.defend(&fire);
dog.defend(&fire);
}
測試輸出如下:

在這個例子中,特質Fight無需在方法attack中使用where語句羅列可能的類型,而是在Person,Animal中具體説明,這樣避免了attack方法看起來可笑。
通過這種方式,rust允許一個特質的方法可以和不同的類型關聯起來,而又不會讓特質看起來醜陋怪異,難於維護(以後要增加怎麼辦?)
四、小結
利用關聯類型,特質可以更加方便地定義和管理類型,同時也達成了通用的目的。
總體而言,這是一個不錯的特性。