rust学习二十.5、RUST特质中的关联类型

这是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允许一个特质的方法可以和不同的类型关联起来,而又不会让特质看起来丑陋怪异,难于维护(以后要增加怎么办?)

四、小结

利用关联类型,特质可以更加方便地定义和管理类型,同时也达成了通用的目的。

总体而言,这是一个不错的特性。

 

posted @ 2025-03-17 17:02  正在战斗中  阅读(40)  评论(0)    收藏  举报