Rust Trait
Table of Contents
1. Rust Trait
1.1. Overview
use std::cell::RefCell; use std::rc::Rc; trait Move { // 默认实现 fn walk(&self) { println!("{}", "default walk"); } fn run(&self) -> (); } struct Cat(); impl Move for Cat { fn run(&self) -> () { println!("{}", "cat run"); } } struct Dog(); impl Move for Dog { fn run(&self) -> () { println!("{}", "dog run"); } fn walk(&self) -> () { println!("{}", "dog walk"); } } struct Donkey(); impl Move for Donkey { fn run(&self) -> () { println!("{}", "donkey run"); } fn walk(&self) -> () { println!("{}", "donkey walk"); } } fn do_move_static<T>(t: &T) where T: Move, { t.run(); t.walk(); } fn do_move_static_2(t: &impl Move) { t.run(); t.walk(); } fn do_move_dynamaic(t: &Move) { t.run(); t.walk(); } fn do_move_dynamaic_2(t: Box<dyn Move>) { t.run(); t.walk(); } fn do_move_dynamaic_3(t: Rc<dyn Move>) { t.run(); t.walk(); } fn main() { println!("{}", "-------------"); let cat = Cat(); cat.walk(); cat.run(); println!("{}", "-------------"); let dog = Dog(); dog.walk(); dog.run(); println!("{}", "-------------"); do_move_static(&Donkey()); do_move_static_2(&Donkey()); println!("{}", "-------------"); do_move_dynamaic(&Donkey()); do_move_dynamaic_2(Box::new(Cat())); do_move_dynamaic_3(Rc::new(Donkey())); }
------------- default walk cat run ------------- dog walk dog run ------------- donkey run donkey walk donkey run donkey walk ------------- donkey run donkey walk cat run default walk donkey run donkey walk
1.2. static dispatching: trait bounds
1.3. dynamic dispatching: trait object
https://brson.github.io/rust-anthology/1/all-about-trait-objects.html https://alschwalm.com/blog/static/2017/03/07/exploring-dynamic-dispatch-in-rust/
trait Move { fn walk(&self); fn run(&self); } struct Dog(); impl Move for Dog { fn run(&self) -> () { println!("{}", "dog run"); } fn walk(&self) -> () { println!("{}", "dog walk"); } } // error[E0277]: the size for values of type `(dyn Move + 'static)` cannot be // known at compilation time fn do_move_dynamaic(t: Move) { t.run(); t.walk(); } fn main() { do_move_dynamaic(Dog()); }
上面的代码编译出错, 因为 do_move_dynamaic 的参数大小未知: t 的值可能是 Cat 或 Dog, 两种 struct 的大小并不一定相同, 导致无法编译 do_move_dynamaic.
解决的方法是 do_move_dynamaic 接受一个 Move 的 `引用`, 例如 `&Move`, 或 `Box<Move>`, `Rc<Move>`
但实际情况 do_move_dynamaic 并非简单的接受一个引用: 编译器会生成一个新的类型: trait object, 即编译时报错的 `(dyn Move + `static)` 类型. do_move_dynamaic 的参数实际上是 trait object 的引用
trait object 是编译器自动生成的, 其格式大约是:
pub struct TraitObject { pub data: *mut (), pub vtable: *mut (), }
当调用 `do_move_dynamaic(&Dog())` 时, 会生成一个针对 Dog 的 TraitObject:
static move_for_dog_vtable: DogVtable = DogVtable { destructor: /* compiler magic */, size: 2, align: 1, method: walk_of_dog as fn(*const ()), method: run_of_dog as fn(*const ()), }; let dog: Dog = Dog(); // let b: &Foo = &a; let dog_trait = TraitObject { data: &dog, vtable: &move_for_dog_vtable };
当调用 Move.walk 时, 会使用类似的代码:
(dog_trait.vtable.walk_of_dog).(dog_trait.data);
1.4. trait object layout
`call (&||{})` 时, `call` 拿到的参数 `f` 实际是一个自动生成的 trait object.
fn call(f: &dyn Fn()) -> () { println!("\ncall:"); println!("f is at: {:?}", &f as *const _); } fn main() { println!("\nmain:"); let x = -1 as i64; let c = move || { println!("{:?}", x); }; println!("size of closure: {:?}", std::mem::size_of_val(&c)); println!("closure is at: {:?}", &c as *const _); unsafe { println!("closure value: {:x}", *(&c as *const _ as *const i64)); } call(&c); }
使用 rust-gdb 观察:
$> rust-gdb ./Test (gdb) main: size of closure: 8 closure is at: 0x7fffffffd4a0 closure value: ffffffffffffffff call: f is at: 0x7fffffffd380 (gdb) x /2gx 0x7fffffffd380 0x7fffffffd380: 0x00007fffffffd4a0 0x0000555555583180 // 0x00007fffffffd4a0 是 closure 的地址, 0x0000555555583180 是 vtable 的地址 // 所以 trait object 格式为 [closure_addr; vtable_addr] (gdb) x /20gx 0x0000555555583180 0x555555583180: 0x0000555555558470 0x0000000000000008 0x555555583190: 0x0000000000000008 0x0000555555558620 // 一个典型的 vtable 的格式为: // struct FooVtable { // destructor: fn(*mut ()), // size: usize, // align: usize, // method: fn(*const ()) -> (), // } 所以 0x0000555555558620 应该是 closure 的代码: $> nm -C Test|grep closure 666:0000000000004620 t Test::main::{{closure}}