Rust 对多态性的支持构建于两个相关特性之上: 特型(trait)和泛型(generic)

特型是 Rust 对接口或抽象基类的实现。首先,它看起来就像 Java 或 C# 中的接口。比如写字节的特型叫 std::io::Write,它在标准库 中定义的开头是这样的:

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

泛型是 Rust 中另一种多态性的实现。类似 C++ 模板,泛型函数或 类型可以搭配很多种不同类型的值使用。

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

泛型函数

fn run_query<M, R>(data: &DataSet, map: M, reduce: R) ->
Results
    where M: Mapper + Serialize,
		  R: Reducer + Serialize
 
{ ... }

泛型函数可以同时拥有生命期参 数和类型参数。生命期参数在前面

/// 返回距离target点最近的candidates点中的引用  
fn nearest<'t, 'c, P>(target: &'t P, candidates: &'c [P]) -> &'c P
    where P: MeasureDistance
{  
...
}

对 一个泛型函数,Rust 可能会编译很多次,每次生成一种要使用类型的 函数。这样得到的二进制文件会比较大,也就是 C++ 社区中所谓的代码膨胀(code bloat)现象。如今,内存已经不是稀缺资源,我们大 多数人可以忽略代码大小。然而,受限环境依旧存在。 但是每次 Rust 编译器为泛型函数生成机器码时,它 都知道自己要操作的是什么类型,也知道当时要调用哪个 write 方 法。这就消除了动态查找的时间。

对比一下使用特型目标的行为。Rust 在编译时不可能知道特型目标 指向的值的真正类型,只能在运行时确定。因此即使明确传入 Sink, 也无法消除调用虚拟方法和检查错误的成本。

定义和实现特型

impl Visible for Broom

可以定义默认方法

trait Write {
    fn write(&mut self, buf: &[u8]) -> Result<usize>;
    fn flush(&mut self) -> Result<()>;
    fn write_all(&mut self, buf: &[u8]) -> Result<()> {
        let mut bytes_written = 0;
        while bytes_written < buf.len() {
            bytes_written +=
self.write(&buf[bytes_written..])?;
 
}
 
Ok(()) }
... }

Self 作为返回类型意味着 x.clone() 的类型就是 x 的类 型,不管具体什么类型。如果 x 是 String,那 x.clone() 的类型也 是 String,而不是 Clone 或其他实现 Clone 的类型。

pub trait Clone {
    fn clone(&self) -> Self;
 
... }

Rust 特型可以包含静态方法和构造函数

trait StringSet {  
/// 返回一个新的空集合 fn new() -> Self;
 
/// 返回一个包含strings中所有字符串的集合  
fn from_slice(strings: &[&str]) -> Self;
 
/// 确定当前集合是否包含特定的value  
fn contains(&self, string: &str) -> bool;
 
/// 向当前集合中添加一个字符串
    fn add(&mut self, string: &str);
}

完全限定方法调用

"hello".to_string()
str::to_string("hello")

在 “hello”.to_string() 这种形式中,使用的是 . 操作符,并没有 确切说明要调用的是哪个 to_string 方法。Rust 会通过一个方法查 询算法去找,根据类型、强制解引用(deref coercion)等来判断。 完全限定调用要确切指定使用的方法,这在一些边界情况下很有用。

ToString::to_string("hello")
<str as ToString>::to_string("hello")
  • 复习 Rust 特型 trait (@2024-01-22)