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
每月 174 次下载
在 vptr 中使用
8KB
108 行
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