#traits #virtual #thin #vtable #light #dynamic-dispatch

no-std vptr

通过在结构体中嵌入虚表指针,实现对特例对象的瘦引用

3个不稳定版本

0.2.1 2019年9月14日
0.2.0 2019年8月23日
0.1.0 2019年6月8日

#212内存管理

34 每月下载次数

MIT 许可证

35KB
443

Crates.io Documentation

vptr

启用对特例的瘦引用

简介

特例对象和虚表是什么?

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

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 特例对象的引用。内部,这个 &dyn Shape 引用由两个指针组成:一个指向对象的指针和一个指向虚表的指针。虚表是一个静态结构,包含指向 area 函数的函数指针。每个实现特例的类型都有一个虚表,但同一类型的每个实例共享相同的虚表。只有指向结构体的指针是不够的,因为 total_area 不知道它指向的确切类型,所以它不知道应该从哪个 impl 调用 area 函数。

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

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

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

两种方法都有优缺点:在Rust中,对象本身更小,因为它们没有虚表指针。它们还可以实现来自其他crate的特例,这在C++中是不可能的,因为它必须以某种方式将虚表指针放入对象中。但是,Rust中的特例指针是正常指针的两倍大小。这通常不是问题。除非当然,您想在受限的内存中将许多特例对象引用打包到vector中,或者通过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 Shame。区别在于 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

依赖项

~1.5MB
~33K SLoC