Enable thin references to trait
In rust, you can have dynamic dispatch with the so-called Trait object. Here is a typical example
```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 totalarea(list: &[&dyn Shape]) -> f32 {
list.iter().map(|x| x.area()).fold(0., |a, b| a+b)
}
``
In this example the function
totalareatakes a reference of trait objects that implement
the
Shapetrait. Internally, this
&dyn Shapereference is composed of two pointer:
a pointer to the object, and a pointer to a virtual table. The virtual table is a static
structure containing the function pointer to the
areafunction. Such virtual table exist
for each type that implements the trait, but each instance of the same type share the same
virtual table. Having only a pointer to the struct itself would not be enough as the
total_areadoes not know the exact type of what it is pointed to, so it would not know from
which
implto call the
area` function.
This box diagram shows a simplified representation of the memory layout
ascii
&dyn Shape ╭──────> Rectangle ╭─> vtable of Shape for Rectangle
┏━━━━━━━━━━━━━┓ │ ┏━━━━━━━━━┓ │ ┏━━━━━━━━━┓
┃ data ┠───╯ ┃ w ┃ │ ┃ area() ┃
┣━━━━━━━━━━━━━┫ ┣━━━━━━━━━┫ │ ┣━━━━━━━━━┫
┃ vtable ptr ┠─────╮ ┃ h ┃ │ ┃ drop() ┃
┗━━━━━━━━━━━━━┛ │ ┗━━━━━━━━━┛ │ ┣━━━━━━━━━┫
╰────────────────────╯ ┃ size ┃
╏ ╏
Other languages such as C++ implements that differently: in C++, each instance of a dynamic class has a pointer to the virtual table, inside of the class. So just a normal pointer to the base class is enough to do dynamic dispatch
Both approaches have pros and cons: in Rust, the object themselves are a bit smaller as they do not have a pointer to the virtual table. They can also implement trait from other crates which would not work in C++ as it would have to somehow put the pointer to the virtual table inside the object. But rust pointer to trait are twice as big as normal pointer. Which is usually not a problem. Unless of course you want to pack many trait object reference in a vector in constrained memory, or pass them through ffi to C function that only handle pointer as data. That's where this crate comes in!
This crates allows to easily opt in to thin references to trait for a type, by having pointers to the virtual table within the object.
```rust use vptr::vptr; 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: &[vptr::ThinRef
Same as before, but we added #[vptr(Shape)]
and are now using ThinRef<Shape>
instead of
&dyn Shame
. The difference is that the ThinRef has only the size of one pointer
ascii
ThinRef<Shape> Rectangle ╭─>VTableData ╭─>vtable of Shape for Rectangle
┏━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━┓ ╮ │ ┏━━━━━━━━┓ │ ┏━━━━━━━━━┓
┃ ptr ┠──╮ ┃ w ┃ │ ╭──│──┨ offset ┃ │ ┃ area() ┃
┗━━━━━━━━━━━━━┛ │ ┣━━━━━━━━━━━━┫ ⎬─╯ │ ┣━━━━━━━━┫ │ ┣━━━━━━━━━┫
│ ┃ h ┃ │ │ ┃ vtable ┠──╯ ┃ drop() ┃
│ ┣━━━━━━━━━━━━┫ ╯ │ ┗━━━━━━━━┛ ┣━━━━━━━━━┫
╰──>┃ vptr_Shape ┠──────╯ ┃ size ┃
┗━━━━━━━━━━━━┛ ╏ ╏
#[vptr]
macroThe #[vptr(Trait)]
macro can be applied to a struct and it adds members to the struct
with pointer to the vtable, these members are of type VPtr, where S is the struct.
The macro also implements the HasVPtr
trait which allow the creation of ThinRef
for this
You probably want to derive from Default
, otherwise, the extra fields needs to be initialized
manually (with Default::default()
or VPtr::new()
)
```rust trait Shape { fn area(&self) -> f32; }
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::
// 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., vptrShape: VPtr::new(), vptrToString: VPtr::new() };
// Also work with tuple struct
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
struct MyString(String);
impl PartialEq
MIT