Rust 特型与泛型

  • Vec<T> 是泛型的:你可以创建任意类型值的向量,包括在你的程序中定义的连 Vec 的作者都不曾设想的类型。
  • 许多类型有 .write() 方法,包括 File 和 TcpStream。你的代码可以通过引用来获取任意写入器,并向它发送数据。你的代码不必关心写入器的类型。以后,如果有人添加了一种新型写入器,你的代码也能够直接支持。

Java 中很常用我就简单过了

特型(trait)是 Rust 体系中的接口或抽象基类。

trait Write {
    fn write(&mut self, buf: &[u8]) -> Result<usize>;
    fn flush(&mut self) -> Result<()>;
    fn write_all(&mut self, buf: &[u8]) -> Result<()> { ... }
    ...
}

如何使用

use std::io::Write;

fn say_hello(out: &mut dyn Write) -> std::io::Result<()> {
    out.write_all(b"hello world\n")?;
    out.flush()
}

泛型是 Rust 中多态的另一种形式。

/// 给定两个值,找出哪个更小
fn min<T: Ord>(value1: T, value2: T) -> T {
    if value1 <= value2 {
        value1
    } else {
        value2
    }
}

此函数中的 表明 min 函数可以与实现了 Ord 特型的任意类型(任意有序类型)T 的参数一起使用。

使用特型

特型是一种语言特性,我们可以说某类型支持或不支持某个特型。大多数情况下,特型代表着一种能力,即一个类型能做什么。

其实感觉就很像是 Java 中的抽象类,其他类实现了抽象那么就有了该特性.

  • 实现了 std::io::Write 的值能写出一些字节。
  • 实现了 std::iter::Iterator 的值能生成一系列值。
  • 实现了 std::clone::Clone 的值能在内存中克隆自身。
  • 实现了 std::fmt::Debug 的值能用带有 {:?} 格式说明符的 println!() 打印出来。

[!IMPORTANT]

特型本身必须在作用域内。否则,它的所有方法都是不可见的:

//错误
let mut buf: Vec<u8> = vec![];
buf.write_all(b"hello")?;  // 错误:没有名为`write_all`的方法

//在这种情况下,编译器会打印出一条友好的错误消息,建议添加 use std::io::Write;,这确实可以解决问题:
use std::io::Write;

let mut buf: Vec<u8> = vec![];
buf.write_all(b"hello")?;  // 正确

特型对象

在 Rust 中使用特型编写多态代码有两种方法:特型对象和泛型。

Rust 不允许 dyn Write 类型的变量:

use std::io::Write;

let mut buf: Vec<u8> = vec![];
let writer: dyn Write = buf;  // 错误:`Write`的大小不是常量

变量的大小必须是编译期已知的,而那些实现了 Write 的类型可以是任意大小。

对特型类型(如 writer)的引用叫作特型对象。

let mut buf: Vec<u8> = vec![];
let writer: &mut dyn Write = &mut buf;  // 正确

泛型函数与类型参数

通过指定函数类型,进行设置输入函数类型.

trait Car {
    fn new() -> Self
    where
        Self: Sized;
    fn drive(&self);
}

trait Car2 {
    fn new() -> Self
    where
        Self: Sized;
    fn drive(&self);
}

struct Benz {
    name: String,
    model: String,
    year: i32,
}

struct BMW {
    name: String,
    model: String,
    year: i32,
}

impl Car2 for BMW {
    fn new() -> Self {
        BMW {
            name: "BMW".to_string(),
            model: "X5".to_string(),
            year: 2020,
        }
    }

    fn drive(&self) {
        println!("{} {} is driving to {}", self.name, self.model, self.year);
    }
}
impl Car for Benz {
    fn new() -> Self {
        Benz {
            name: "Benz".to_string(),
            model: "S500".to_string(),
            year: 2020,
        }
    }

    fn drive(&self) {
        println!("{} {} is driving to {}", self.name, self.model, self.year);
    }
}

fn drive_car<C: Car>(car: C) {
    car.drive();
}
//重要 通过 where 来指定每个特性属于什么
fn drive_car2<C, D>(car: C, car2: D)
where
    C: Car,
    D: Car2,
{
    car.drive();
    car2.drive();
}

fn main() {
    drive_car2(Benz::new(), BMW::new());
}

使用哪一个

关于是使用特型对象还是泛型代码的选择相当微妙。由于这两个特性都基于特型,因此它们有很多共同点。

trait Vegetable {
    ...
}

struct Salad<V: Vegetable> {
    veggies: Vec<V>
}

struct Salad {
    veggies: Vec<dyn Vegetable>  // 错误:`dyn Vegetable`的大小不是常量
}

//使用 Box 限定一个
struct Salad {
    veggies: Vec<Box<dyn Vegetable>>
}

定义与实现特型

定义特型很简单,给它一个名字并列出特型方法的类型签名即可。如果你正在编写游戏,那么可能会有这样的特型:

/// 角色、道具和风景的特型——游戏世界中可在屏幕上看见的任何东西
trait Visible {
    /// 在给定的画布上渲染此对象
    fn draw(&self, canvas: &mut Canvas);

    /// 如果单击(x, y)时应该选中此对象,就返回true
    fn hit_test(&self, x: i32, y: i32) -> bool;
}

impl Visible for Broom {
    fn draw(&self, canvas: &mut Canvas) {
        for y in self.y - self.height - 1 .. self.y {
            canvas.write_at(self.x, y, '|');
        }
        canvas.write_at(self.x, self.y, 'M');
    }

    fn hit_test(&self, x: i32, y: i32) -> bool {
        self.x == x
        && self.y - self.height - 1 <= y
        && y <= self.y
    }
}

添加一个辅助方法来支持 Broom::draw(),就必须在单独的 impl 块中定义它:

impl Broom {
    /// 供下面的Broom::draw()使用的辅助函数
    fn broomstick_range(&self) -> Range<i32> {
        self.y - self.height - 1 .. self.y
    }
}

默认方法

如同 Java8 的功能,抽象类也是可以提供默认方法的,实现的对象可以不进行实现.

trait Car {
    fn get_name(&self) -> &str;

    fn say_name(&self) {
        println!("I am a {}", self.get_name());
    }
}

struct Benz;

impl Car for Benz {
    fn get_name(&self) -> &str {
        "Benz"
    }
}


fn main() {
    let cat = Benz;
    cat.say_name();
}

特型与其他人的类型

Rust 允许在任意类型上实现任意特型,但特型或类型二者必须至少有一个是在当前 crate 中新建的。这意味着任何时候如果你想为任意类型添加一个方法,都可以使用特型来完成:

trait IsEmoji {
    fn is_emoji(&self) -> bool;
}

/// 为内置的字符类型实现IsEmoji特型
impl IsEmoji for char {
    fn is_emoji(&self) -> bool {
        ...
    }
}

assert_eq!('$'.is_emoji(), false);

在实现特型时,特型或类型二者必须至少有一个是在当前 crate 中新建的。这叫作孤儿规则

特型中的 Self

在第一个 impl 中,Self 只是 CherryTree 的别名,而在第二个 impl 中,它是 Mammoth 的别名。

pub trait Spliceable {
    fn splice(&self, other: &Self) -> Self;
}

impl Spliceable for CherryTree {
    fn splice(&self, other: &Self) -> Self {
        ...
    }
}

impl Spliceable for Mammoth {
    fn splice(&self, other: &Self) -> Self {
        ...
    }
}

子特型

/// 游戏世界中的生物,既可以是玩家,也可以是
/// 其他小精灵、石像鬼、松鼠、食人魔等
trait Creature: Visible {
    fn position(&self) -> (i32, i32);
    fn facing(&self) -> Direction;
    ...
}
//每个实现了 Creature 的类型也必须实现 Visible 特型:
impl Visible for Broom {
    ...
}

impl Creature for Broom {
    ...
}
//可以用以下写法
trait Creature where Self: Visible {
    ...
}

类型关联函数

在大多数面向对象语言中,接口不能包含静态方法或构造函数,但特型可以包含类型关联函数,这是 Rust 对静态方法的模拟:

trait StringSet {
    /// 返回一个新建的空集合
    fn new() -> Self;

    /// 返回一个包含`strings`中所有字符串的集合
    fn from_slice(strings: &[&str]) -> Self;

    /// 判断这个集合中是否包含特定的`string`
    fn contains(&self, string: &str) -> bool;

    /// 把一个字符串添加到此集合中
    fn add(&mut self, string: &str);
}

fn main{
	let set1 = SortedStringSet::new();
  let set2 = HashedStringSet::new();
}

如果想使用 &dyn StringSet 特型对象,就必须修改此特型,为每个未通过引用接受 self 参数的关联函数加上类型限界 where Self: Sized:

trait StringSet {
    fn new() -> Self
        where Self: Sized;

    fn from_slice(strings: &[&str]) -> Self
        where Self: Sized;

    fn contains(&self, string: &str) -> bool;

    fn add(&mut self, string: &str);
}

完全限定的方法调用

迄今为止,我们看到的所有调用特型方法的方式都依赖于 Rust 为你补齐了一些缺失的部分。

当两个方法具有相同的名称时。

生拼硬凑的经典示例是 Outlaw(亡命之徒),它具有来自不同特型的两个 .draw() 方法,一个用于将其绘制在屏幕上,另一个用于犯罪。

outlaw.draw();  // 错误:画(draw)在屏幕上还是拔出(draw)手枪?

Visible::draw(&outlaw);  // 正确:画在屏幕上
HasPistol::draw(&outlaw);  // 正确:拔出手枪

当无法推断 self 参数的类型时。

let zero = 0;  // 类型未指定:可能为`i8`、`u8`……

zero.abs();  // 错误:无法在有歧义的数值类型上调用方法`abs`

i64::abs(zero);  // 正确

将函数本身用作函数类型的值时。

let words: Vec<String> =
    line.split_whitespace()  // 迭代器生成&str值
        .map(ToString::to_string)  // 正确
        .collect();