卿搞笔 发表于 3 天前

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.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;

}
#
struct Person {
    name: String,
    age: u32,
    sex: String,
}
#
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允许一个特质的方法可以和不同的类型关联起来,而又不会让特质看起来丑陋怪异,难于维护(以后要增加怎么办?)
四、小结

利用关联类型,特质可以更加方便地定义和管理类型,同时也达成了通用的目的。
总体而言,这是一个不错的特性。
 

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: rust学习二十.5、RUST特质中的关联类型