Rust 的多态性:
特型:Rust 对接口或抽象基类的实现。
如下写字节的特型 std::io::Write
:
trait Write {
fn write(&mut self, buf: &[u8]) -> Result<usize>;
fn flus(&mut self) -> Result<()>;
fn write_all(&mut self, buf: &[u8]) -> Result<()> { ... }
...
}
内置特型是 Rust 语言为操作符重载及其其他特性提供的钩子(hook)。
泛型:泛型函数或类型可以搭配很多种不同类型的值使用。
/// 给定两个值,取出较小的值
fn min<T: Ord>(value1: T, value2: T) -> T {
if value1 <= value2 {
value1
} else {
value2
}
}
<T: Ord>
代表 min
可以使用实现 Ord
特型的任意类型参数 T;T: Ord
叫做绑定。std::io::Write
的值可以执行写字节操作;std::iter::Iterator
的值可以产生值的序列;std::clone::Clone
的值可以在内存中克隆自身;std::fmt::Debug
的值可以使用 println!()
的 {:?}
格式说明符打印出来。std::fs::File
实现了 Write
特型,可以将字节写入本地文件。std::net::TcpStream
则是将字节写入网络连接。Vec<u8>
同样实现了 Write
。在字节向量上每次调用.write()
,都可以在向量末尾追加一些数据。Range<i32>
实现了 Iterator
特型,与切片、散列表等相关的其他爹大气类型也实现了这个特型。TcpStream
这种不仅仅表示内存中数据的类型外,大多数标准库类型都实现了 Clone
特型。Debug
特型。Clone
和 Iterator
方法,默认一直在作用域中。它们是标准前置模块的一部分,Rust 会自动将其导入到所有模块中。Rust 有两种方式使用特型编写多态化代码:特型目标与泛型。
Rust 不允许声明 Write
类型的变量:因为变量的大小必须在编译时就知道,而实现 Write
的类型可以是任意大小。
特型目标:指向一个特型类型(如下面的 writer
)的引用,具有引用的共同性:指向某个值,有生命期,可以是 mut
或共享引用。
let mut buf: Vec<u8> = vec![];
let writer: &mut Write = &mut buf;
特型目标的不同之处:Rust 在编译时通常不知道引用目标的类型。
特型目标是一个胖指针,包含指向值的指针和指向表示该值类型的表的指针。每个特型目标都占用两个机器字。
Rust 在必要时会自动将普通引用转换为特型目标。
let mut local_file = File::create("hello.txt")?; // &mut local_file的类型是&mut File
say_hello(&mut local_file)?; // say_hello的参数类型是&mut Write
如下 say_hello()
泛型函数,接收一个特型目标作为参数。
fn say_hello<W: Write>(out: &mut W) -> std::io::Result<()> {
out.write_all(b"hello world\n")?;
out.flush()
}
<W: Write>
是类型参数(type parameter),表示实现了 Write
特型的某种类型。泛型函数可以有多个类型参数:
fn run_query<M: Mapper + Serialize, R: Reducer + Serialize>(data: &DataSet, map: M, reduce: R) -> Results { ... }
上述代码的绑定显得比较长,可以使用 where
关键字,将上述代码结构清晰的表述出来:
fn run_query<M, R>(data: &DataSet, map: M, reduce: R) -> Results where M: Mapper + Serialize, R: Reducer + Serialize { ... }
这样的类型参数 M
和 R
仍然是提前声明,但是绑定转移到了后面。
where
子句也适用于泛型结构体、枚举、类型别名和方法等,任何允许使用绑定的地方。
泛型函数可以同时拥有生命期参数和类型参数。生命期参数写在前面:
fn nearest<'t, 'c, P>(target: &'t P, candidates: &'c [P]) -> &'c P where P: MeasureDistance {
...
}
除了函数外,其他特性也支持泛型:
泛型结构体
泛型枚举
泛型方法,不管定义该方法的类型是不是泛型的。
impl PancakeStack {
fn push<T: Topping>(&mut self, goop: T) -> PancakeResult<()> {
...
}
}
泛型类型别名
type PancakeResult<T> = Result<T, PancakeError>;
泛型特型
绑定、where
子句、生命期参数等,可以适用于上述所有泛型特性项。
定义特型:给它命名并列出特型方法的类型签名:
trait Visible {
fn draw(&self, canvas: &mut Canvas);
fn hit_test(&self, x: i32, y: i32) -> bool;
}
实现特型,使用 impl TraitName for Type
语法:
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
}
}
特型 impl
中定义的,都是特型的特性。
如果要添加一个支持 Broom::draw()
的辅助方法,那么就在单独的 impl
块中定义它:
impl Broom {
fn broomstick_range(&self) -> Range<i32> {
self.y - self.height - 1 .. self.y
}
}
impl Visible for Broom {
fn draw(&self, canvas: &mut Canvas) {
for y in self.broomstick_range() {
...
}
...
}
...
}
标准库的 Write
特型的定义包含一个 write_all
的默认实现:
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(())
}
...
}
write
和 flush
方法是每个书写器必须实现的基本方法;write_all
,如果没有,那就会使用上面的默认实现。标准库中默认方法最多的是 Iterator
特型,它有一个必须方法(.next()
)和其他很多默认方法。
Rust 允许在任意类型上实现任意特型,只要当前包中导入了相关特性或类型即可。
每当想给任何类型添加方法时,都可以使用特型来完成:
trait IsEmoji {
fn is_emoji(&self) -> bool;
}
/// 为内置字符类型实现IsEmoji方法,这种特型称为扩展特型
impl IsEmoji for char {
fn is_emoji(&self) -> bool {
...
}
}
assert_eq!('$'.is_emoji(), false);
可以使用泛型 impl
块,让一个类型家族一次性全部实现一个扩展特型。
use std::io::{self, Write};
/// 为可以发送HTML的值定义扩展特型
trait WriteHtml {
fn write_html(&mut self, html: &HtmlDocument) -> io::Result<()>;
}
/// 这样就可以向任意std::io::writer写入HTML了
// 对每个实现了Write的类型W,在这里为W再实现特型WriteHtml
impl<W: Write> WriteHtml for W {
fn write_html(&mut self, html: &HtmlDocument) -> io::Result<()> {
...
}
}
serde
为所有相关类型都添加了一个.serialize()
方法:
use serde::Serialize;
use serde_json;
pub fn save_configuration(config: &HashMap<String, String>) -> std::io::Result<()> {
// 创建一个JSON序列化处理程序,将数据写入文件
let writer = File::create(config_filename())?;
let mut serializer = serde_json::Serializer::new(writer);
// 剩下的任务交给serde的.serialize()方法处理
config.serialize(&mut serializer)?;
Ok(())
}
连贯规则(coherence rule):在实现特型时,相关的特型或类型必须都一个在当前包中是新的。确保特型实现的唯一性。
Self
特型可以使用 Self
关键字作为类型。
pub trait Clone {
fn clone(&self) -> Self;
...
}
Self
作为返回类型:x.clone()
的类型就是 x
的类型。下述特型有两个实现:
pub trait Spliceable {
fn splice(&self, other: &Self) -> Self
}
// self和other的类型必须完全一样
impl Spliceable for CherryTree {
fn splice(&self, other: &Self) -> Self { // 此处Self是CherryTree的别名
...
}
}
impl Spliceable for Mammoth {
fn splice(&self, other: &Self) -> Self { // 此处Self是Mammoth的别名
...
}
}
使用 Self
类型的特型于特型目标不能共存:
// 错误:特型Spliceable不能成为引用目标
fn splice_anything(left: &Spliceable, right: &Spliceable) {
let combo = left.splice(right);
...
}
left
和 right
是不是同一种类型特型目标只能针对最简单的特型实现。这种特型可以使用 Java 中的接口,或者 C++ 中的抽象基类实现。
特型的高级特型,不能与特型目标共存。
如何设计一个目标友好的特型:
pub trait MegaSpliceable {
fn splice(&self, other: &MegaSpliceable) -> Box<MegaSpliceable>;
}
// 调用.splice()方法时,other参数的类型不必跟self的类型完全一样
// 只要两者的类型都是MegaSpliceable就可以通过类型检查
可以将一个特型声明为另一个特型的扩展
// 所有与角色Creature有关的代码,也可以使用来自Visible特型的方法
trait Creature: Visible {
fn position(&self) -> (i32, i32);
fn facing(&self) -> Direction;
...
}
所有实现 Creature
的类型,必须也实现 Visible
特型:
impl Visible for Broom {
...
}
impl Creature for Broom {
...
}
子类型类似 Java 或 C# 的字接口,是一个特型要用另外几个扩展已有特型的表达式。
不同于其他面向对象语言,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);
}
所有实现 StringSet
特型的类型,都必须实现这 4 个关联函数
前两个函数不接收 self
参数,它们充当构造函数。在非泛型代码中,这些函数可以使用::
语法调用,就跟调用其他静态方法一样:
// 创建两个实现了StringSet的假设类型的集合
let set1 = SortedStringSet::new();
let set2 = HashedStringSet::new();
在泛型代码中,类型是可变的:
/// 返回document中不在wordlis中的词的集合
fn unknown_words<S: StringSet>(document: &Vec<String>, wordlist: &S) -> S {
let mut unknowns = S::new();
for word in document {
if !wordlist.contains(word) {
unknowns.add(word);
}
}
unknowns
}
特型目标不支持静态方法:如果要使用特型目标 &StringSet
,就必须修改特型,给每个静态方法都添加 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);
}
&StringSet
特型目标,可以调用.contains()
和.add()
方法。方法其实是一种特殊的函数:
"hello".to_string()
// 等价于
str::to_string("hello")
to_string
是标准 ToString
特型的方法,上述代码等价于:
ToString::to_string("hello")
<str as ToString>::to_string("hello")
上述 4 种方法调用,完成相同的操作:
value.method()
这种形式比较常用;完全限定方法调用,要确切指定使用的方法,应用场景有:
两个方法同名
outlaw.draw(); // 错误,.draw()方法中包含outlaw方法
Visible::draw(&outlaw);
HasPistol::draw(&outlaw);
无法推断 self
参数的类型
let zero = 0; // 类型未指定
zeor.abs(); // 错误,没有找到abs方法
i64::abs(zero); // 可以
将函数本身作为值
let words: Vec<String> =
line.split_whitespace() // 产生&str值的迭代器
.map(<str as ToString>::to_string) // 可以
.collect();
在宏里调用特型方法。
完全限定语法也适用于静态方法。
std::iter::Iterator
特型通过自身产生值的类型,将不同迭代器类型关联起来。
迭代器:能够通过它遍历一系列值的对象。
pub trait Iterator {
type Item; // 关联类型(associated type)
fn next(&mut self) -> Option<Self::Item>;
...
}
所有实现 Iterator
的类型,都必须指定自己产生的项(item)的类型。
实现一个 Iterator
的类型:
// std::env标准库模块的部分代码
impl Iterator for Args {
type Item = String; // 类型声明
fn next(&mut self) -> Option<String> {
...
}
...
}
泛型代码可以使用关联类型:
/// 遍历一个迭代器,将它们的值存储到一个新变量中
fn collect_into_vector<I: Iterator>(iter: I) -> Vec<I::Item> {
let mut results = Vec::new();
for value in iter {
results.push(value);
}
results
}
关联类型在特型需要覆盖除一个方法之外的定义时也很常用:
一个线程池的库中的 Task
特型(表示工作单元),可以包含一个关联的 Output
特型;
一个 Pattern
特型(表示搜索字符串的一种方式),可以包含一个关联的 Match
类型,表示模式与字符串匹配之后收集到的所有信息:
trait Patern {
type Match;
fn search(&self, string: &str) -> Option<Self::Match>;
}
/// 可以在字符串中搜索特定的字符
impl Pattern for char {
/// Match(匹配)表示的时发现字符的位置
type Match = usize;
fn search(&self, string: &str) -> Option<usize> {
...
}
}
一个操作关系数据库的库可以有一个 DatabaseConnection
特型,可以有表示事务、指针、初始化语句等的关联函数。
std::ops::Mul
特型关联可以参与乘法运算的类型。
/// 支持*操作符的类型实现的特型
pub trait Mul<RHS> { // RHS:Right Hand Side右手边
/// 应用*操作符之后返回的类型结果
type Output;
/// *操作符对应的方法
fn mul(self, rhs: RHS) -> Self::Output;
}
lhs * rhs
是 Mul::mul(lhs, rhs)
的简写rand::random
工作原理rand
中既包含一个跟随机数生成器有关的特型 rand::Rng
,还包含一个能够被随机生成的类型有关的特型 rand::Rand
。如下面的代码:
use std::ops::{Add, Mul};
fn dot<N>(v1: &[N], v2: &[N]) -> N
where N: Add<Output=N> + Mul<Output=N> + Default + Copy // 对N的绑定
{
let mut total = N::default();
for i in 0..v1.len() {
total = total + v1[i] * v2[i];
}
total
}
#[test]
fn test_dot() {
assert_eq!(dot(&[1, 2, 3, 4], &[1, 1, 1, 1]), 10);
assert_eq!(dot(&[53.0, 7.0], &[1.0, 5.0]), 88.0);
}
对 N
的绑定进行了逆向工程,让编译器当指导,反复检查自己的工作
Number
特型包含所有操作符和方法。
逆向工程绑定的优点:
详见《Rust 程序设计》(吉姆 - 布兰迪、贾森 - 奥伦多夫著,李松峰译)第十一章
原文地址