正式开始
类型体系的实质:对类型进行界说、检查和处理的工具,保证了某个操作处理的数据类型是咱们所希望的 特设多态:包括运算符重载,是指同一种行为有许多不同的完成; 子类型多态:把子类型当成父类型运用,比方 Cat 当成 Animal 运用
trait
trait 是 Rust 中的接口,它界说了类型运用这个接口的行为
基本trait
示例:std::io::Write 接口
pub trait Write {
fn write(&mut self, buf: &[u8]) -> Result<usize>;
fn flush(&mut self) -> Result<()>;
fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result<usize> { ... }
fn is_write_vectored(&self) -> bool { ... }
fn write_all(&mut self, buf: &[u8]) -> Result<()> { ... }
fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> Result<()> { ... }
fn write_fmt(&mut self, fmt: Arguments<'_>) -> Result<()> { ... }
fn by_ref(&mut self) -> &mut Self where Self: Sized { ... }
}
- 在 trait 中界说的办法亦被称作相关函数,其能够有缺省的完成
- Self 代表当时的类型,比方 File 类型完成了 Write,那么完成过程中运用到的 Self 就指代 File。
- self 在用作办法的第一个参数时,实际上是 self: Self 的简写,所以 &self 是 self: &Self, 而 &mut self 是 self: &mut Self
- 在完成 trait 的时分,也能够用泛型参数来完成 trait
trait办法是怎样调用的呢
以 fn write_all(&mut self, buf: &[u8]) -> Result<()>
;
buf.write_all(b"Hello world!").unwrap();
为例
- 它承受两个参数:&mut self 和 &[u8]
- 第一个参数传递的是 buf 这个变量的可变引证
- 第二个参数传递的是 b”Hello world!”
带相关类型的 trait
- Rust 答应 trait 内部包括相关类型,完成时跟相关函数相同,它也需求完成相关类型
- trait 办法里的参数或许回来值,都能够用相关类型来表述,而在完成有相关类型的 trait 时,只需求额外供给相关类型的详细类型即可
pub trait Parse {
type Error;
fn parse(s: &str) -> Result<Self, Self::Error>;
}
支撑泛型的 trait
pub trait Add<Rhs = Self> {
type Output;
#[must_use]
fn add(self, rhs: Rhs) -> Self::Output;
}
这儿 Rhs 默认是 Self,也便是说你用 Add trait ,假如不供给泛型参数,那么加号右值和左值都要是相同的类型
- 泛型 trait 能够让咱们在需求的时分,对同一种类型的同一个 trait,有多个完成
举例
// Service trait 答应某个 service 的完成能处理多个不同的 Request
pub trait Service<Request> {
type Response;
type Error;
// Future 类型受 Future trait 束缚
type Future: Future;
fn poll_ready(
&mut self,
cx: &mut Context<'_>
) -> Poll<Result<(), Self::Error>>;
fn call(&mut self, req: Request) -> Self::Future;
}
- 留意对于某个确定的 Request 类型,只会回来一种 Response,所以这儿 Response 运用相关类型,而非泛型。
- 假如有或许回来多个 Response,那么应该运用泛型 Service
trait 的“承继”
- 在 Rust 中,一个 trait 能够“承继”另一个 trait 的相关类型和相关函数
- 比方 trait B: A,trait B 在界说时能够运用 trait A 中的相关类型和办法
如何做子类型多态?
假如一个目标 A 是目标 B 的子类,那么 A 的实例能够出现在任何希望 B 的实例的上下文中
fn name(animal: impl Animal) -> &'static str; 等于 fn name<T: Animal>(animal: T) -> &'static str;
- 这种泛型函数会根据详细运用的类型被单态化,编译成多个实例,是静态分配。
trait object
- 咱们要有一种手段告知编译器,此处需求并且仅需求任何完成了 Formatter 接口的数据类型。
- 在 Rust 里,这种类型叫 Trait Object,表现为 &dyn Trait 或许 Box<dyn Trait> dyn 关键字仅仅用来协助咱们更好地区分普通类型和 Trait 类型,阅览代码时,看到 dyn 就知道后面跟的是一个 trait 了
Trait Object 的完成机理
当需求运用 Formatter trait 做动态分配时,将一个详细类型的引证赋给 &Formatter
- HtmlFormatter 的引证赋值给 Formatter 后,会生成一个 Trait Object
- Trait Object 的底层逻辑便是胖指针。其中,一个指针指向数据自身,另一个则指向虚函数表(vtable)
vtable
- vtable 是一张静态的表
- Rust 在编译时会为运用了 trait object 的类型的 trait 完成生成一张表,放在可执行文件中(一般在 TEXT 或 RODATA 段)
- 一个类型+Trait生成一张表
在这张表里,包括详细类型的一些信息,如 size、aligment 以及一系列函数指针:
- 这个接口支撑的一切办法
- 详细类型的 drop trait,当 Trait object 被开释,它用来开释其运用的一切资源
- C++ / Java 指向 vtable 的指针,在编译时放在类结构里,而 Rust 放在 Trait object 中。这也是为什么 Rust 很简单对原生类型做动态分配,而 C++/Java 不可
目标安全的
只要满足目标安全的 trait 才干运用 trait object
- 假如 trait 一切的办法,回来值是 Self 或许带着泛型参数,那么这个 trait 就不能发生 trait object
- 不答应回来 Self,是因为 trait object 在发生时,原来的类型会被抹去,所以 Self 究竟是谁不知道
- 不答应带着泛型参数,是因为 Rust 里带泛型的类型在编译时会做单态化,而 trait object 是运行时的产品,两者不能兼容
小结
好用链接
- io Write 缺省完成
- BufBuilder 的Write trait
- Rust 正则表达式
- 字符串转数字
- Add trait
- Towner service
- gRPC tonic
- tokio AsyncWriteExt
- futures SteamExt
- 目标安全
- 迭代器 trait
- 完成async fn 的 trait比较困难
延伸阅览
运用 trait 有两个留意事项
-
在界说和运用 trait 时,咱们需求遵循孤儿规则(Orphan Rule)
a. trait 和完成 trait 的数据类型,至少有一个是在当时 crate 中界说的
b. 也便是说,你不能为第三方的类型完成第三方的 trait,当你测验这么做时,Rust 编译器会报错
-
Rust 对含有 async fn 的 trait ,还没有一个很好的被标准库承受的完成
vtable 会为每个类型的每个 trait 完成生成一张表
经过以下代码追踪它的行为
use std::fmt::{Debug, Display};
use std::mem::transmute;
fn main() {
let s1 = String::from("hello world!");
let s2 = String::from("goodbye world!");
// Display / Debug trait object for s
let w1: &dyn Display = &s1;
let w2: &dyn Debug = &s1;
// Display / Debug trait object for s1
let w3: &dyn Display = &s2;
let w4: &dyn Debug = &s2;
// 强行把 triat object 转换成两个地址 (usize, usize)
// 这是不安全的,所以是 unsafe
let (addr1, vtable1): (usize, usize) = unsafe { transmute(w1) };
let (addr2, vtable2): (usize, usize) = unsafe { transmute(w2) };
let (addr3, vtable3): (usize, usize) = unsafe { transmute(w3) };
let (addr4, vtable4): (usize, usize) = unsafe { transmute(w4) };
// s 和 s1 在栈上的地址,以及 main 在 TEXT 段的地址
println!(
"s1: {:p}, s2: {:p}, main(): {:p}",
&s1, &s2, main as *const ()
);
// trait object(s / Display) 的 ptr 地址和 vtable 地址
println!("addr1: 0x{:x}, vtable1: 0x{:x}", addr1, vtable1);
// trait object(s / Debug) 的 ptr 地址和 vtable 地址
println!("addr2: 0x{:x}, vtable2: 0x{:x}", addr2, vtable2);
// trait object(s1 / Display) 的 ptr 地址和 vtable 地址
println!("addr3: 0x{:x}, vtable3: 0x{:x}", addr3, vtable3);
// trait object(s1 / Display) 的 ptr 地址和 vtable 地址
println!("addr4: 0x{:x}, vtable4: 0x{:x}", addr4, vtable4);
// 指向同一个数据的 trait object 其 ptr 地址相同
assert_eq!(addr1, addr2);
assert_eq!(addr3, addr4);
// 指向同一种类型的同一个 trait 的 vtable 地址相同
// 这儿都是 String + Display
assert_eq!(vtable1, vtable3);
// 这儿都是 String + Debug
assert_eq!(vtable2, vtable4);
}
精选问答
-
对于 Addtrait,假如咱们不用泛型,把 Rhs 作为 Add trait 的相关类型,能够么?为什么?
不能够。相关类型只能impl一次,咱们需求为Complex完成多个Add<Rhs>
a. trait 泛型是对同一个数据结构需求有多个不同的完成
b. trait 的相关类型是,在某个完成里,我需求设定和这个完成相关的类型。其实相关类型就和相关函数相同的
-
如下代码能编译经过么,为什么?
use std::{fs::File, io::Write};
fn main() {
let mut f = File::create("/tmp/test_write_trait").unwrap();
let w: &mut dyn Write = &mut f;
w.write_all(b"hello ").unwrap();
let w1 = w.by_ref();
w1.write_all(b"world").unwrap();
}
不能。回来类型中的 Self 需求是Sized,而 dyn Write 不是Sized