3个不稳定版本
0.2.1 | 2019年9月14日 |
---|---|
0.2.0 | 2019年8月23日 |
0.1.0 | 2019年6月8日 |
#212 在 内存管理
34 每月下载次数
35KB
443 行
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