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}}

1.5. built-in trait

1.5.1. Drop

1.5.2. Clone

1.5.3. Copy

1.5.4. Deref & DerefMut

1.5.5. From & To

1.5.6. AsRef & AsMut

1.5.7. FromIterator

1.5.8. IntoIterator

1.5.9. Default

1.5.10. Debug

1.5.11. Fn & FnMut & FnOnce

1.5.12. PartialOrd, PartialEq

Author: [email protected]
Date: 2019-01-17 Thu 00:00
Last updated: 2024-08-05 Mon 17:56

知识共享许可协议