Crates.io Documentation

vptr

Enable thin references to trait

Intro

What are trait object and virtual table ?

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 functiontotalareatakes a reference of trait objects that implement theShapetrait. 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 theareafunction. 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 whichimplto call thearea` 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!

Thin references

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; }

[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]) -> f32 { list.iter().map(|x| x.area()).fold(0., |a, b| a+b) } ```

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 ┃ ┗━━━━━━━━━━━━┛ ╏ ╏

The #[vptr] macro

The #[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; }

[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::::from(&r1); asserteq!(mem::sizeof::>(), mem::sizeof::()); asserteq!(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., vptrShape: VPtr::new(), vptrToString: 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")]

[derive(Default)]

struct MyString(String); impl PartialEq for MyString { fn eq(&self, other: &str) -> bool { self.0 == other } } let mystr = MyString("Hi".tostring(), VPtr::new()); let mystringref = ThinRef::from(&mystr); assert!(*mystring_ref == *"Hi"); ```

License

MIT