zoukankan      html  css  js  c++  java
  • Rust:Trait

    1、Trait是什么?

    一个Trait描述了一种抽象接口(找不到很合适的词),这个抽象接口可以被类型继承。Trait只能由三部分组成(可能只包含部分):

    • functions(方法)
    • types(类型)
    • constants(常量)

    所有的Trait都定义了一个隐含类型Self,其指向实现该Trait的类型。Traits可能也包含额外的类型参数,这些类型参数(包括Self),与往常一样可能受到其他Traits等的约束。

    类型需要通过独立的implementations去实现不同的Trait

    trait中不必须提供与trait关联的条目的实际定义(类型别名的实际类型、函数的函数体、常数的值表达式),是可选的。如果Trait提供了定义,该定义即为任何实现它的类型的默认行为(如果对应类型没有override的话);如果Trait未提供定义,则任何实现它的类型都必须提供一个定义。

    2、Selfself

    Self:实现Trait的类型的别名

    self:方法参数 fn f(self) {},等价于fn f(self: Self) {}

    同理有:

    &self等价于self: &Self

    &mut self等价于self: &mut Self

    3、默认实现 + 无Override

     trait Hello {
         fn say_hi(&self) {
             println!("hi");
         }
     }
     
     struct Student {}
     impl Hello for Student {}
     struct Teacher {}
     impl Hello for Teacher {}
     
     fn main() {
         let s = Student {};
         s.say_hi();
         let t = Teacher {};
         t.say_hi();
     }

    默认实现 + Override

     trait Hello {
         fn say_hi(&self) {
             println!("hi");
         }
     }
     
     struct Nobody {}
     impl Hello for Nobody {}
     struct Teacher {}
     impl Hello for Teacher {
         fn say_hi(&self) {
             println!("hi, I'm teacher Lee.");
         }
     }
     
     fn main() {
         let n = Nobody {};
         n.say_hi();
         let t = Teacher {};
         t.say_hi();
     }

    无默认实现

     trait Hello {
         fn say_hi(&self);
     }
     
     struct Student {}
     impl Hello for Student {
         fn say_hi(&self) {
             println!("hi, I'm Jack.");
         }
     }
     struct Teacher {}
     impl Hello for Teacher {
         fn say_hi(&self) {
             println!("hi, I'm teacher Lee.");
         }
     }
     
     fn main() {
         let s = Student {};
         s.say_hi();
         let t = Teacher {};
         t.say_hi();
     }

    4、孤儿规则(Orphan rule)

    如果我们有如下定义,会是什么样呢?

     use std::ops::Add;
     
     impl Add<i32> for i32 {
         type Output = i32;
         fn add(self, other: i32) -> Self::Output {
             self - other
         }
     }
     
     fn main() {}

    会发生编译错误,其中一个错误为:

     error[E0117]: only traits defined in the current crate can be implemented for arbitrary types
      --> srcmain.rs:3:1
       |
     3 | impl Add<i32> for i32 {
       | ^^^^^--------^^^^^---
       | |    |            |
       | |    |            `i32` is not defined in the current crate
       | |    `i32` is not defined in the current crate
       | impl doesn't use only types from inside the current crate
       |
       = note: define and implement a trait or new type instead

    这个就是孤儿规则在起作用:当你为某类型实现某 trait 的时候,该类型或者trait至少有一个是在当前 crate 中定义的,你不能为第三方的类型实现第三方的 trait 。

    5、Trait与泛型

     trait Hello<T> {
         fn world<T>(&self, i: T) -> String;
     }

    上述就是一个简单的携带泛型T的trait

    6、Trait用途一:接口抽象

     trait Hello {
         fn say_hi(&self) {
             println!("hi");
         }
     }
     
     struct Student {}
     impl Hello for Student {}
     
     fn main() {
         let s = Student {};
         s.say_hi();
     }

    7、Trait用途二:泛型约束

    比如要编写一个函数,但是只能对指定类型生效,这个时候可以借助Trait作为泛型的约束,称之为Trait BoundTrait限定),当然Trait Bound可以同时存在多个,用"+"连接

     trait Run {
     }
     trait Eat {
     }
     #[derive(Debug)]
     struct Horse {
     }
     impl Run for Horse {
     }
     impl Eat for Horse {
     }
     fn demo<T: Run + Eat>(x: T) {}
     //如下方式是等价的
     //fn demo<T>(x: T) where T: Run + Eat {}
    

    泛型约束还可以作用于trait本身

    trait Learning {}
    trait Teaching: Learning {}
    
    struct Student {}
    impl Learning for Student {}
    
    struct Teacher {}
    impl Learning for Teacher {}
    impl Teaching for Teacher {}

    表明只有实现了 Learning 的trait类型才能实现 Teaching trait

    8、Trait用途三:作为抽象类型 - Trait Object(Trait对象,动态分发)

    当我们只关心某个类型是否实现了特定trait,而不关注其具体类型的时候,就该Trait Object(Trait对象)登场了,请看下面两个例子的差别,前者使用泛型+Trait bound,后者使用Trait对象

    泛型模式:

     trait Run {}
     struct Human {}
     impl Run for Human {}
     struct Cat {}
     impl Run for Cat {}
     fn demo<T>(x: Vec<Box<T>>) where T: Run {}
     fn main() {
         let mut v = vec![];
         v.push(Box::new(Human {}));
         //v.push(Box::new(Cat {}));  //这行会导致编译失败
         demo(v);
     }

    Trait对象模式:

     trait Run {}
     struct Human {}
     impl Run for Human {}
     struct Cat {}
     impl Run for Cat {}
     fn demo(x: Vec<Box<dyn Run>>) {}
     fn main() {
         let mut v: Vec<Box<dyn Run>> = vec![];
         v.push(Box::new(Human {}));
         v.push(Box::new(Cat {}));
         demo(v);
     }

    Trait object的本质是指针,它可以指向不同的类型,指向的具体类型不同,调用的方法也不同。

    对象安全

    一个Trait Object是指实现了一组Traits的某个类型的不确定值,这组Traits由对象安全的Trait以及auto traits组成,即一个Trait是对象安全的,如果满足:

    • Trait本身是没有Self: Sized约束
    • 所有方法都是Object Safe(对象安全)的

    所有方法都必须满足如下约束才能成为是Object Safe

    • 函数有Self: Sized约束,
    • -------------或者满足如下所有条件---------------
    • 函数不能有泛型参数
    • 第一个参数必须为Self类型或者可以解引用为Self的类型(目前包含self&self&mut selfself:: Box<Self>
    • 其他参数或者返回值均不能使用Self类型

    9、Trait用途四:作为抽象类型 - impl Trait

    impl TraitName可以作为返回值类型(更准确的说,impl TraitName并不是一个类型),也可以作为函数参数,2018版本,impl Trait只能出现在这两个位置。该使用方式在Closure和Iterator中十分有用,以后介绍;

     trait Run {
     }
     struct Human {
     }
     impl Run for Human {
     }
     fn demo() -> impl Run {
         Human {}
     }
     fn main() {
     }

    特别需要指出的是,函数的不同分支的返回值需要为同一个具体类型,如下方式将会编译失败。

     trait Run {
     }
     struct Human {
     }
     impl Run for Human {
     }
     struct Cat {
     }
     impl Run for Cat {
     }
     fn demo(x: i32) -> impl Run {
         if x > 0 {
             Human {}
         } else {
             Cat {}
         }
     }
     fn main() {
     }

    10、Trait中的所有权

    Trait方法如果接受了 self 类型的参数,则会消耗类型的值自身,比如

     trait Run {
         fn run_and_die(self);
     }
     #[derive(Debug)]
     struct Horse {
     
     }
     impl Run for Horse {
         fn run_and_die(self) {
     
         }
     }
     fn main() {
         let h = Horse {};
         h.run_and_die();
         //println!("{:?}", h);  //这行会导致失败,因为run_and_die会消耗自己
     }

    11、Derive

    编译器允许你通过 #[derive] 属性自动实现一些Trait,这些Trait包含:

    • 比较相关的:EqPartialEqOrdPartialOrd
    • Clone,经由&T创建T
    • Copy,实现T的复制语义
    • Hash,计算&T的哈希值
    • Default,创建数据类型的空实例
    • Debug,使得可以用 {:?}格式化T

    你既可以通过#[derive]自动实现这些Trait,也可以自己手动去impl这些Trait

     #[derive(Debug,Copy, Clone)]
     struct Person {}

    12、Unsafe Trait

    带有unsafe关键字定义的Trait,使用unsafe Trait 是safe的,一个类型实现unsafe Trait的时候,必须使用unsafe impl作为前缀,比如SendSyncunsafe的,则impl这两个Trait如下

     unsafe impl Send for Student {}

    13、常见的Trait

    a. DerefDerefMut : 可以用来重载 *操作符,也可以用来 method resolution 以及 deref coercions(解引用转换)

    b. Drop: 用于解构,在一个类型的变量被销毁前执行

    c. Copy :如果类型实现了这个Trait,则该类型的值会使用拷贝语义替代移动语义,并避免所有权的变更。一个类型实现Copy有两个前提条件:1、这个类型不能实现Drop;2、这个类型的所有字段都必须为Copy的。

    编译器会为以下类型自动实现Copy ,除此之外的类型如果想要impl Copy必须先impl Clone

    • Numeric types
    • charbool 以及 !
    • Copy类型组成的Tuples
    • Copy类型组成的Arrays
    • Shared references(共享引用/借用)
    • Raw pointers(裸指针)

    d. CloneCopy的超集,它也是需要编译器生成implementations,编译器会为以下类型自动实现Clone

    • 内置实现了Copy的类型
    • Clone类型组成的Tuples
    • Clone类型组成的Arrays

    e. Send:表明一个类型的值是否可以安全的在线程间传递。

    f. Sync:表明一个类型的值是否可以安全的在线程间共享。

    g. Sized:编译器可确定大小的类型

    i. Unsize:编译器无法确定大小的类型

    14、Operator Traits

    std::cmp中定义的Traits

    std::ops中定义的Traits

    可以用来重载操作符,slice索引表达式(indexing expressions) 以及 调用表达式(call expressions)

    15、Auto traits

    SendSyncUnwindSafeRefUnwindSafe

    16、消除Trait歧义(Disambiguating overlapping traits)

    假设为某一个类型,实现了两个Trait,这两个Trait刚好有同名的method,这个时候如何区分呢?

     trait TraitOne {
         fn action(&self) {
             println!("action of trait one!")
         }
     }
     trait TraitTwo {
         fn action(&self) {
             println!("action of trait two!");
         }
     }
     struct Person {}
     impl TraitOne for Person {}
     impl TraitTwo for Person {}
     fn main() {
         let p = Person {};
         <Person as TraitOne>::action(&p);
         <Person as TraitTwo>::action(&p);
         //如下方式会编译失败
         //p.action();
     }

    17、参考资料

    Reference:

    rust by example:

  • 相关阅读:
    nginx的linux服务器内核参数调整【转】
    从运维角度来分析mysql数据库优化的一些关键点【转】
    10 个 MySQL 经典错误【转】
    keepalived的vip无法ping通【原创】
    MySQL数据库的锁详解【转】
    CGI,FastCGI,PHP-CGI与PHP-FPM区别详解【转】
    vim块编辑删除、插入、替换【转】
    Keepalived两节点出现双VIP情况及解决方法【原创】
    通过全备+relaylog同步恢复被drop的库或表【转】
    MySQL伪master+Binlog+同步【转】
  • 原文地址:https://www.cnblogs.com/dream397/p/14188674.html
Copyright © 2011-2022 走看看