Stories

Detail Return Return

rust學習二十.5、RUST特質中的關聯類型 - Stories Detail

這是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允許一個特質的方法可以和不同的類型關聯起來,而又不會讓特質看起來醜陋怪異,難於維護(以後要增加怎麼辦?)

四、小結

利用關聯類型,特質可以更加方便地定義和管理類型,同時也達成了通用的目的。

總體而言,這是一個不錯的特性。

 

Add a new Comments

Some HTML is okay.