# abi-vtable-macro
`#[abi_vtable]` — annotate a Rust trait and get the complete C contract for
[dyn-loader](https://crates.io/crates/dyn-loader)'s **abi mode**:
a `#[repr(C)]` vtable struct, one `extern "C"` thunk per method, a static
vtable instance, a `#[no_mangle]` getter — and a host-side safe wrapper
(`CalcHost`) with one safe method per trait method — all generated.
```rust
use abi_vtable_macro::abi_vtable;
#[abi_vtable(name = "calc")]
pub trait Calc {
fn add(&self, a: i32, b: i32) -> i32;
fn is_even(&self, x: i32) -> bool;
}
struct MyCalc;
impl Calc for MyCalc {
fn add(&self, a: i32, b: i32) -> i32 { a + b }
fn is_even(&self, x: i32) -> bool { x % 2 == 0 }
}
// One line: static instance + thunks + vtable + getter.
abi_vtable_impl_calc!(MyCalc, MyCalc);
```
The host (Rust, C, C++, Zig, ...) loads `calc_get_vtable` and calls through
the struct of function pointers — see dyn-loader's `AbiTable`. A Rust host
that declares the same trait with the same `#[abi_vtable]` attribute also
gets the generated `CalcHost` wrapper and calls it safely:
```rust,ignore
let module = unsafe { AbiTable::<CalcVtable>::load(path, b"calc_get_vtable\0")? };
let host = CalcHost::new(module.vtable(), std::ptr::null_mut());
let sum = host.add(2, 3); // safe — no unsafe block needed
```
## Rules
- Field order of the generated vtable **is** the protocol: it follows trait
method declaration order. Never reorder after release.
- All fn pointers are `extern "C"` (cdecl), ctx-leading (`*mut c_void` first).
- `$instance` must be const-constructible (unit struct, `const fn`, literal).
## License
MIT