4 个版本

新版本 0.2.2 2024年8月25日
0.2.1 2019年9月14日
0.2.0 2019年8月23日
0.1.0 2019年6月8日

#141 in #virtual

Download history 1/week @ 2024-04-28 1/week @ 2024-07-07

每月 174 次下载
vptr 中使用

MIT 许可证

8KB
108

Crates.io Documentation

vptr

启用对 trait 的瘦引用

简介

什么是 trait 对象和虚表?

在 Rust 中,您可以使用所谓的 trait 对象进行动态分派。以下是一个典型的例子

trait Shape { fn area(&self) -> f32; }
struct Rectangle { w: f32, h : f32 }
impl Shape for Rectangle { fn area(&self) -> f32 { self.w * self.h } }
struct Circle { r: f32 }
impl Shape for Circle { fn area(&self) -> f32 { 3.14 * self.r * self.r } }

// Given an array of Shape, compute the sum of their area
fn total_area(list: &[&dyn Shape]) -> f32 {
    list.iter().map(|x| x.area()).fold(0., |a, b| a+b)
}

在这个例子中,函数 total_area 接受实现了 Shape trait 的 trait 对象的引用。内部,这个 &dyn Shape 引用由两个指针组成:一个指向对象的指针和一个指向虚表的指针。虚表是一个静态结构,包含指向 area 函数的函数指针。每个实现该 trait 的类型都有一个虚表,但同一类型的每个实例共享相同的虚表。仅有一个指向结构本身的指针是不够的,因为 total_area 并不知道它所指向的确切类型,因此它不知道从哪个 impl 调用 area 函数。

此框图显示了内存布局的简化表示

   &dyn Shape      ╭──────> Rectangle     ╭─> vtable of Shape for Rectangle
 ┏━━━━━━━━━━━━━┓   │       ┏━━━━━━━━━┓    │        ┏━━━━━━━━━┓
 ┃ data        ┠───╯       ┃ w       ┃    │        ┃ area()  ┃
 ┣━━━━━━━━━━━━━┫           ┣━━━━━━━━━┫    │        ┣━━━━━━━━━┫
 ┃ vtable ptr  ┠─────╮     ┃ h       ┃    │        ┃ drop()  ┃
 ┗━━━━━━━━━━━━━┛     │     ┗━━━━━━━━━┛    │        ┣━━━━━━━━━┫
                     ╰────────────────────╯        ┃ size    ┃
                                                   ╏         ╏

其他语言,如 C++,实现方式不同:在 C++ 中,每个动态类的实例都有一个指向虚表的指针,位于类内部。因此,只需要一个指向基类的普通指针就足以进行动态分派

这两种方法都有优缺点:在 Rust 中,对象本身稍微小一点,因为它们没有虚表的指针。它们还可以实现来自其他 crate 的 trait,这在 C++ 中是无法实现的,因为它必须以某种方式将虚表的指针放入对象中。但是,Rust 中对 trait 的指针是普通指针的两倍大。这通常不是问题。除非当然您想在受限制的内存中将许多 trait 对象引用打包到向量中,或者通过 ffi 将它们传递给仅处理指针作为数据的 C 函数。这就是这个 crate 的用武之地!

瘦引用

这个创建器允许通过对象中的虚拟表指针,轻松启用对类型特质的薄引用。

use vptr::vptr;
trait Shape { fn area(&self) -> f32; }
#[vptr(Shape)]
struct Rectangle { w: f32, h : f32 }
impl Shape for Rectangle { fn area(&self) -> f32 { self.w * self.h } }
#[vptr(Shape)]
struct Circle { r: f32 }
impl Shape for Circle { fn area(&self) -> f32 { 3.14 * self.r * self.r } }

// Given an array of Shape, compute the sum of their area
fn total_area(list: &[vptr::ThinRef<dyn Shape>]) -> f32 {
    list.iter().map(|x| x.area()).fold(0., |a, b| a+b)
}

与之前相同,但我们添加了 #[vptr(Shape)] 并现在使用 ThinRef<Shape> 而不是 &dyn Shape。区别在于 ThinRef 只有指针的大小。

 ThinRef<Shape>        Rectangle          ╭─>VTableData  ╭─>vtable of Shape for Rectangle
 ┏━━━━━━━━━━━━━┓      ┏━━━━━━━━━━━━┓ ╮    │  ┏━━━━━━━━┓  │     ┏━━━━━━━━━┓
 ┃ ptr         ┠──╮   ┃ w          ┃ │ ╭──│──┨ offset ┃  │     ┃ area()  ┃
 ┗━━━━━━━━━━━━━┛  │   ┣━━━━━━━━━━━━┫ ⎬─╯  │  ┣━━━━━━━━┫  │     ┣━━━━━━━━━┫
                  │   ┃ h          ┃ │    │  ┃ vtable ┠──╯     ┃ drop()  ┃
                  │   ┣━━━━━━━━━━━━┫ ╯    │  ┗━━━━━━━━┛        ┣━━━━━━━━━┫
                  ╰──>┃ vptr_Shape ┠──────╯                    ┃ size    ┃
                      ┗━━━━━━━━━━━━┛                           ╏         ╏

#[vptr]

#[vptr(Trait)] 宏可以应用于结构体,并为结构体添加指向虚拟表的指针成员,这些成员的类型为 VPtr,其中 S 是结构体。该宏还实现了 HasVPtr 特质,允许为此创建 ThinRef

你可能想从 Default 继承,否则,需要手动初始化额外的字段(使用 Default::default()VPtr::new()

trait Shape { fn area(&self) -> f32; }
#[vptr(Shape, ToString)] // There can be several traits
#[derive(Default)]
struct Rectangle { w: f32, h : f32 }

// The traits within #[vptr(...)] need to be implemented for that type
impl Shape for Rectangle { fn area(&self) -> f32 { self.w * self.h } }
impl Display for Rectangle {
  fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
     write!(fmt, "Rectangle ({} x {})", self.w, self.h)
  }
}

// [...]
let mut r1 = Rectangle::default();
r1.w = 10.; r1.h = 5.;
let ref1 = ThinRef::<dyn Shape>::from(&r1);
assert_eq!(mem::size_of::<ThinRef<dyn Shape>>(), mem::size_of::<usize>());
assert_eq!(ref1.area(), 50.);

// When not initializing with default, you must initialize the vptr's manually
let r2 = Rectangle{ w: 1., h: 2., ..Default::default() };
let r3 = Rectangle{ w: 1., h: 2., vptr_Shape: VPtr::new(), vptr_ToString: VPtr::new() };

// Also work with tuple struct
#[vptr(Shape)] struct Point(u32, u32);
impl Shape for Point { fn area(&self) -> f32 { 0. } }
let p = Point(1, 2, VPtr::new());
let pointref = ThinRef::from(&p);
assert_eq!(pointref.area(), 0.);

// The trait can be put in quote if it is too complex for a meta attribute
#[vptr("PartialEq<str>")]
#[derive(Default)]
struct MyString(String);
impl PartialEq<str> for MyString {
    fn eq(&self, other: &str) -> bool { self.0 == other }
}
let mystr = MyString("Hi".to_string(), VPtr::new());
let mystring_ref = ThinRef::from(&mystr);
assert!(*mystring_ref == *"Hi");

许可证

MIT


lib.rs:

请参阅 vptr crate 的文档

依赖关系

~1.5MB
~36K SLoC