Skip to content

Latest commit

 

History

History
309 lines (217 loc) · 7.37 KB

17.高级 trait.md

File metadata and controls

309 lines (217 loc) · 7.37 KB

高级 trait

关联类型

pub trait Iterator {
    type Item;

    fn next(&mut self) -> Option<Self::Item>;
}

默认泛型参数

泛型类型指定默认类型的语法是在声明泛型类型时使用 <PlaceholderType=ConcreteType>

典型示例:运算符重载

Add trait

trait Add<RHS=Self> {
    type Output;

    fn add(self, rhs: RHS) -> Self::Output;
}

说明:
RHS 是一个泛型类型参数(“right hand side” 的缩写)。这里用到了默认泛型参数语法,如果实现 Add trait 时不指定 RHS 的具体类型,RHS 的类型将是默认的 Self 类型。

示例1:

use std::ops::Add;

#[derive(Debug, PartialEq)]
struct Point {
    x: i32,
    y: i32,
}

impl Add for Point {
    type Output = Point;

    fn add(self, other: Point) -> Point {
        Point {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
}

fn main() {
    assert_eq!(Point { x: 1, y: 0 } + Point { x: 2, y: 3 },
               Point { x: 3, y: 3 });
}

示例2(覆盖默认泛型参数):

use std::ops::Add;

struct Millimeters(u32);
struct Meters(u32);

impl Add<Meters> for Millimeters {
    type Output = Millimeters;

    fn add(self, other: Meters) -> Millimeters {
        Millimeters(self.0 + (other.0 * 1000))
    }
}

完全限定语法

Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法,也不能阻止为同一类型同时实现这两个 trait。甚至直接在类型上实现开始已经有的同名方法也是可能的。

关联方法上的限定

trait Pilot {
    fn fly(&self);
}

trait Wizard {
    fn fly(&self);
}

struct Human;

impl Pilot for Human {
    fn fly(&self) {
        println!("This is your captain speaking.");
    }
}

impl Wizard for Human {
    fn fly(&self) {
        println!("Up!");
    }
}

impl Human {
    fn fly(&self) {
        println!("*waving arms furiously*");
    }
}

fn main() {
    let person = Human;
    Pilot::fly(&person);
    Wizard::fly(&person);
    person.fly();
}

在方法名前指定 trait 名向 Rust 澄清了我们希望调用哪个 fly 实现。因为 fly 方法获取一个 self 参数,如果有两个 类型 都实现了同一 trait,Rust 可以根据 self 的类型计算出应该使用哪一个 trait 实现。

关联函数上的完全限定语法

trait Animal {
    fn baby_name() -> String;
}

struct Dog;

impl Dog {
    fn baby_name() -> String {
        String::from("Spot")
    }
}

impl Animal for Dog {
    fn baby_name() -> String {
        String::from("puppy")
    }
}

fn main() {

    // 调用的是 Dog 上的关联函数
    println!("A baby dog is called a {}", Dog::baby_name());

    // 我们希望调用 Animal 上的 baby_name 方法

    // 错误,关联函数没有 self 参数,无法计算具体是用哪一个实现
    println!("A baby dog is called a {}", Animal::baby_name());

    // 正确,完全限定语法
    println!("A baby dog is called a {}", <Dog as Animal>::baby_name());
}

父 trait

示例:

use std::fmt;

trait OutlinePrint: fmt::Display {
    fn outline_print(&self) {
        let output = self.to_string();
        let len = output.len();
        println!("{}", "*".repeat(len + 4));
        println!("*{}*", " ".repeat(len + 2));
        println!("* {} *", output);
        println!("*{}*", " ".repeat(len + 2));
        println!("{}", "*".repeat(len + 4));
    }
}

struct Point {
    x: i32,
    y: i32,
}

// 实现父  OutlinePrint 的同时,还需要单独实现 Display
impl OutlinePrint for Point {}

impl fmt::Display for Point {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}

newtype 模式

用于在外部类型上实现外部 trait

示例:

use std::fmt;

struct Wrapper(Vec<String>);

impl fmt::Display for Wrapper {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "[{}]", self.0.join(", "))
    }
}

fn main() {
    let w = Wrapper(vec![String::from("hello"), String::from("world")]);
    println!("w = {}", w);
}

注意:
如果希望新类型拥有其内部类型的每一个方法,为封装类型实现 Deref trait 并返回其内部类型。否则,只需自行实现所需的方法即可。

DST

动态大小类型(dynamically sized types)的概念。这有时被称为 “DST” 或 “unsized types”,这些类型允许我们处理只有在运行时才知道大小的类型。

比如:

let s1: str = "Hello there!";
let s2: str = "How's it going?";

Rust 需要知道应该为特定类型的值分配多少内存,同时所有同一类型的值必须使用相同数量的内存。如果允许编写这样的代码,也就意味着这两个 str 需要占用完全相同大小的空间,不过它们有着不同的长度。这也就是为什么不可能创建一个存放动态大小类型的变量的原因。

字符串 虽然 &T 是一个储存了 T 所在的内存位置的单个值,&str 则是 两个 值:str 的地址和其长度。于是,&str 就有了一个在编译时可以知道的大小。 这里是 Rust 中动态大小类型的常规用法:他们有一些额外的元信息来储存动态信息的大小。这引出了动态大小类型的黄金规则:必须将动态大小类型的值置于某种指针之后。 此外,也可以将 str 与其它类型的指针结合:比如 Box 或 Rc。

Sized trait

Rust 有一个特定的 trait 来表明一个类型的大小在编译时可知。

示例1:
在泛型中,Rust 隐式的为每一个泛型函数增加了 Sized bound。

fn generic<T>(t: T) {
    // --snip--
}

实际上被当作如下处理:

fn generic<T: Sized>(t: T) {
    // --snip--
}

泛型函数默认只能用于在编译时已知大小的类型,如果在编译时不知道大小,可以使用如下特殊语法来放宽这个限制:

fn generic<T: ?Sized>(t: &T) {
    // --snip--
}

trait 对象

trait 对象指向一个实现了我们指定 trait 的类型的实例,以及一个用于在运行时查找该类型的trait方法的表。我们可以使用 trait 对象代替泛型或具体类型。任何使用 trait 对象的位置,Rust 的类型系统会在编译时确保任何在此上下文中使用的值会实现其 trait 对象的 trait。

在结构体或枚举中,结构体字段中的数据和 impl 块中的行为是分开的,不同于其他语言中将数据和行为组合进一个称为对象的概念中。trait 对象将数据和行为两者相结合,从这种意义上说 则 其更类似其他语言中的对象。不过 trait 对象不同于传统的对象,因为不能向 trait 对象增加数据。

1.使用 trait object

pub struct Screen {
    pub components: Vec<Box<dyn Draw>>,
}

2.使用 泛型 + trait bounds

pub struct Screen<T: Draw> {
    pub components: Vec<T>,
}

impl<T> Screen<T>
    where T: Draw {
    pub fn run(&self) {
        for component in self.components.iter() {
            component.draw();
        }
    }
}

注:
使用后者,限制了 Screen 实例只能有一种泛型实现,而使用 trait 对象的方法,一个 Screen 实例可以存放一个既能包含 Box<Button>,也能包含 Box<TextField>Vec<T>。此外,从编译器角度,前者使用静态分发,后者使用动态分发。