特征对象 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 详细的布局结构会在下一节动态派发部分有更全面的介绍.