12 releases (2 stable)

1.1.0 Jul 20, 2024
1.0.0 Jun 21, 2024
0.3.0 Dec 6, 2023
0.2.1 Apr 5, 2023
0.1.7 Oct 3, 2022

#150 in Procedural macros

MIT license

65KB
1.5K SLoC

VTable Gen

This crate provides macros to generate C++-ABI VTables by defining the structure and vtable layout. It also supports VTable inheritance and basic class inheritance.

Examples

Check out tests.rs, which is pretty self-explanatory.

Usage

Base Structs

  • Define a structure that contains virtual functions
  • Define a structure for the VTable that exactly matches the name of the structure it belongs to, followed by VTable exactly. Example:
struct Foo {}
struct FooVTable {}
  • Mark both the VTable and structure with #[gen_vtable]. Any function pointers you include in the VTable struct will require implementation in an automatically-generated <name>Virtuals trait. Complete Example:
#[gen_vtable]
struct Foo {}
#[gen_vtable]
struct FooVTable {
    foo: extern "C" fn(this: &Foo) -> u32;
}
impl FooVirtuals for Foo {
    extern "C" fn foo(this: &Foo) -> u32 { todo!() }
}

Derived Structs

  • Define structures exactly as with base structures
  • Include the attribute base. Example:
#[gen_vtable]
struct Foo {}
#[gen_vtable]
struct FooVTable {}

#[gen_vtable(base = "Foo")]
struct Bar {}
#[gen_vtable(base = "Foo")]
struct BarVTable {}

Constructing Structs with VTables

Constructing structs with VTables is easy. If the struct is default-able, simply derive DefaultVTable instead of Default. This will impl Default. If the struct isn't default-able, define some function fn new(/* args */) -> Self. Mark the function with new_with_vtable, supplying base structs if necessary as in Derived Structs. For the compiler to know the type, you must either explicitly replace Self as the return type with the type itself, or specify self_type. Here's a verbose example:

// ...
impl Bar {
    #[new_with_vtable(self_type = "Bar")]
    fn new(baz: u32) -> Self {
        Self { baz }
    }
}

which is also equivalent to

// ...
impl Bar {
    #[new_with_vtable]
    fn new(baz: u32) -> Bar {
        Self { baz }
    }
}

If there is a base struct that requires its new function to be called, you will have to also explicitly initialize a base_with_vtbl member with the new constructor of the child type. For example:

// ...
impl Bar {
    #[new_with_vtable(base = "Foo", self_type = "Bar")]
    fn new(baz: u32) -> Self {
        Self {
            base_with_vtable: Foo::new(123),
            baz
        }
    }
}

Overriding Functions

Overriding functions is easy. Because all functions are defined in Traits, one can specify for the compiler to not generate implementations for base struct Virtuals with the argument no_base_trait_impl on the VTable (or both for symmetry :)). Example:

// ...
#[gen_vtable(base = "Foo", no_base_trait_impl)]
struct BarVTable {}
// ...
impl FooVirtuals for Bar {
    extern "C" fn some_fn(this: &Foo) {
        // ...
    }
}

The only caveat is you will have to implement all base traits.

Automatic Implementation

For an automatic implementation, in the case of some abstract struct for example, simply supply unimpl as an argument to gen_vtable, and all methods will be implemented with unimplemented!(). Example:

// ...
#[gen_vtable(unimpl)]
struct Foo {}
#[gen_vtable(unimpl)]
struct FooVTable {}

// `FooVirtuals` is implemented for `Foo`

Known Limitations

  • vtable_gen currently does not support generic structs. This is a trivial addition, however, and will likely be added in the future

Dependencies

~1.4–2MB
~38K SLoC