[][src]Crate vptr

Enable light 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

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

In this example the function total_area takes a reference of trait objects that implement the Shape trait. Internally, this &dyn Shape reference 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 area function. 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_area does not know the exact type of what it is pointed to, so it would not know from which impl to call the area function.

This box diagram shows a simplified representation of the memory layout

   &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!

Light references

This crates allows to easily opt in to light references to trait for a type, by having pointers to the virtual table within the object.

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: &[LightRef<dyn Shape>]) -> 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 LightRef<Shape> instead of &dyn Shame. The difference is that the LightRef has only the size of one pointer

 LightRef<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<S, Trait>, where S is the struct. The macro also implements the HasVPtr trait which allow the creation of LightRef for this

You probably want to derive from Default, otherwise, the extra fields needs to be initialized manually (with Default::default() or 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 = LightRef::<dyn Shape>::from(&r1);
assert_eq!(mem::size_of::<LightRef<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 = LightRef::from(&p);
assert_eq!(pointref.area(), 0.);

Re-exports

pub use vptr_macros::vptr;

Structs

LightBox

A Box of a trait with a size of size_of::<usize>

LightRef

A light reference (size = size_of::<usize>()) to an object implementing the trait Trait

LightRefMut

A light reference (size = size_of::<usize>()) to an object implementing the trait Trait

VPtr

Represent a pointer to a virtual table to the trait Trait that is to be embedded in a structure T

VTableData

The data structure generated by the #[vptr] macro

Traits

HasVPtr

This trait indicate that the type has a VPtr field to the trait Trait