< 返回我的博客

爱国的张浩予 发表于 2021-03-21 14:06

Tags:conversion,traits

按照【来源】与【目标】的配对方式可以分成三类:

从【值】TypeA 至【值】TypeB

由【目标类型】TypeB实现From<TypeA> Trait

impl From<TypeA> for TypeB

自从rustc 1.42+版本以后,由于From<T> Trait【全覆盖实现blanket implementation(即,给·泛型类型·而不是·具体类型·实现Trait)】新特性,rustc会给【来源类型】TypeA自动提供Into<TypeB> Trait实现。即,

  1. 徒手实现:

     impl From<TypeA> for TypeB
    
  2. 买一送一:

    impl Into<TypeB> for TypeA
    

划算不划算,惊喜不惊喜?

另一方面,【类型转换】造成的副作用也包括:

  1. TypeA变量的【所有权转移】。即,在【类型转换】之后,原保存TypeA实例的变量就“废”了。
  2. TypeB新实例被创建。即,这是有内存消耗成本的。其就是使用TypeA实例的内部数据创建一个TypeB实例。然后,对此曰为:“类型转换”。

    他们亏心不亏心呀?

典型使用案例【错误处理】

将来自第三方库的【-错误类型】(即,这些类型被当作Error来用和放到Result<T, E>枚举类里,却没有实现Error Trait - 这是人该干的事吗?)自动转换成自定义的【-错误类型】。

然后,我们才能够,对所有满足Error trait bound的类型,使用动态分派的Box<dyn Error>进行统一捕获处理。

于是,咱们的代码才有了那么点OOP抽象的意思。

期间,From<TypeA> Trait吸引开发者使用的重要噱头就是:?操作符能够自动

  1. 识别实现了From<TypeA> Trait的类型
  2. 和执行类型转换

代码看着好【优雅】,很适合有代码“洁癖”的程序员。

从【值】TypeA至【引用】&TypeB

我个人理解的分享

TypeB的引用】既能够指向它自己类型的值,还可以指向另一个类型 【TypeA的值 】。即,

  1. TypeA既不被复制,也没被所有权转移 --- 这个From<T> Trait明显不同。
  2. 同时,甩出一个既指向【TypeA值】的且“形状”又不同的【TypeB引用】

这又分成两种情况:

  • 借入作为
  • 仅引用

借入作为

要求TypeATypeB以完全相同的方式实现了

  • Eq Trait
  • Ord Trait
  • Hash Trait

这种类型转换模式被称为:TypeA被“借入作为”TypeB

于是,需要给【来源类型】TypeA实现Borrow<T> TraitBorrowMut<T> Trait

  • impl Borrow<TypeB> for TypeA
    
  • impl BorrowMut<TypeB> for TypeA
    

由于Borrow<T> Trait接收【泛型类型参数】,所以同一个【来源类型】TypeA能够被“装配”多个 Borrow<T>实现块和同时被“借入作为”多个不同的【目标类型】TypeB1, TypeB2, ...】。

典型使用案例HashMap<String, V>

HashMap<String, V>既能接受String值作为【键】添加新【键-值对】,又能够接受&str引用作为【键】来检索/删除【键-值对】,正是因为String能够被借入作为str

看一眼HashMap<K, V>中,insertget两个成员方法的签名(特别是泛型类型参数的Trait Bound)就一目了然了:

  1. pub fn insert(&self, key: K, value: V) -> Option<V>
    where K: Hash + Eq;
    

    KString时,接收String类型的【键】来添加新【键-值对】

  2. pub fn get<Q>(&self, k: &Q) -> Option<&V>
    where K: Borrow<Q>,
          Q: Hash + Eq + ?Sized;
    

    KString时,因为K能够被借入作为Q,所以get成员方法就可以接收&str引用为【键】来检索【值】。

这一切都是因为有了

impl Borrow<str> for String

HashMap<K, V>这类用法曾经一度让我特别地困惑。我也下功夫读了不少官方文档,但可能是因为英文水平不足吧,始终云里雾里,理解与认识都不能自恰。直到精读了HashMap<K, V>源码看到了Borrow<T> Trait和再精读了Borrow<T> TraitAPI手册,才算是有了那么点理解的感觉。

仅引用

TypeATypeB是否与如何实现

  • Eq Trait
  • Ord Trait
  • Hash Trait

没有限制。

只要给【来源类型】TypeA实现AsRef<T> TraitAsMut<T> Trait即可。

  •  impl AsRef<TypeB> for TypeA
    
  •  impl AsMut<TypeB> for TypeA
    

典型使用案例-伪函数重载

利用Trait Bound,让函数在同一个形参槽位上既能接收String类型实参,也能接收&str类型的实参

fn test(s: impl AsRef<str>) -> ()

,仅只因为

  • impl AsRef<str> for String
    
  • impl AsRef<str> for str
    

从【引用】&TypeA 至【值】TypeB

这也分类两种模式:

  • 刷复本模式
  • 去引用模式(即,重载去引用操作符 *)

刷复本模式

要求TypeA实现ToOwned<Owned=T> TraitOwned是“关联类型”)。ToOwned Trait 可以被看作是Clone Trait的**“全地形”版本**,因为Clone Trait仅只是允许同类型之间的“刷复本”,而ToOwned Trait允许跨类型之间“刷复本”(比如,从&TypeA引用克隆出TypeB实例)。

去引用模式

这是实现自定义【智能指针】的重要工具Deref<T> TraitDerefMut<T> Trait。其是真正让你能够“触碰到”被引用的内存位置和修改被保存于那个内存位置上的数据。没有任何复本会被刷出来。

这个主题已经足够单独开讲,这里也就先不展开解释了。

评论区

写评论

还没有评论

1 共 0 条评论, 1 页