特征对象 Trait Objects
特征对象 trait objects 是实现一组特征 trait 的一种类型的不透明值 opaque value,
其没有固定内存大小的值.
这种数值类型类似于数组, 因为其大小不确定, 并不能直接作为参数使用, 需要使用 &dyn Trait 或者 Box<dyn Trait> 的形式.
use std::io::Write;
fn main() {
let mut buf: Vec<u8> = Vec::new();
let writer: &mut dyn Write = &mut buf;
}
这里, writer 就是一个 trait object. 作为对比, 我们看看之前学过的 slice 用.
fn main() {
let vec = vec![1, 2, 3, 4];
let vec_ref: &[i32] = &vec;
}
dyn trait
类似于使用切片引用来存储数组 array 或者 动态数组 vec 一样, 使用 dyn trait 语法来存储 trait object.
先看一个示例程序:
struct Sheep {
naked: bool,
name: &'static str,
}
trait Animal {
fn new(name: &'static str) -> Self;
fn name(&self) -> &'static str;
fn noise(&self) -> &'static str;
fn talk(&self) {
println!("{} says {}", self.name(), self.noise());
}
}
impl Sheep {
fn is_naked(&self) -> bool {
self.naked
}
fn shear(&mut self) {
if self.naked {
println!("{} is already naked...", self.name());
} else {
println!("{} gets a haircut!", self.name);
self.naked = true;
}
}
}
impl Animal for Sheep {
fn new(name: &'static str) -> Sheep {
Sheep { name, naked: false }
}
fn name(&self) -> &'static str {
self.name
}
fn noise(&self) -> &'static str {
if self.is_naked() {
"baaaaaa?"
} else {
"baaaaaah!"
}
}
fn talk(&self) {
println!("{} pauses briefly... {}", self.name, self.noise());
}
}
fn main() {
let mut dolly: Sheep = Animal::new("Dolly");
dolly.talk();
dolly.shear();
dolly.talk();
}
它使用 dyn trait 作为返回值, 因为trait object 对象本身占用的内存大小是不确定的, 我们需要把它用 Box<dyn Trait> 包装起来,
放在堆内存上, 否则编译器就会提示内存大小的错误.
Trait Object 的内存布局
trait object 的引用是一种胖指针(fat pointer), 有两个指针组成; 在 64 位的机器上, 占用 16 个字节:
data ptr指针指向实际对象的内存地址vtable ptr指针指向该对象的vtable, 里面记录了该对象提供的方法; 这个表是相同类型的所有值所共享.
trait object 详细的布局结构会在下一节动态派发部分有更全面的介绍.