dyn-loader
Dynamic library loader with two plugin-loading modes:
dynmode — Rust fat-pointer bridge: load Rust trait objects from.so/.dylibplugin files, with Arc-like retain/release.cdynmode — COM-style C function tables (VTablePlugin<T>): plain#[repr(C)]function-pointer structs, ABI-stable, usable from C/C++/Zig.
Core types:
DynLib: wrapslibloading::LibrarywithArcfor shared ownership.AbiDynFatPtr: ABI-stable representation of a Rust fat pointer (data ptr + vtable ptr),#[repr(C)].AbiStableDynRef: fat pointer + retain/release function pointers, enabling safe cross-boundary Arc-like reference counting.SafeArcDyn<T>: safe, cloneable handle over anAbiStableDynRef.DynPlugin<T>: loaded plugin holding aSafeArcDyn<T>that dereferences to&Tfor calling trait methods on the loaded object.VTablePlugin<T>: loaded COM-style function table (cdyn mode).
⚠️ IMPORTANT — ABI compatibility / compiler version alignment
You must strictly align the Rust compiler version. Every library and executable that exchanges dyn fat pointers across the boundary must be built with the exact same Rust compiler version to guarantee a stable ABI. Rust makes no ABI stability guarantees between compiler releases (vtable layout, metadata encoding, etc. may change). Mixing compiler versions between the host and plugins is undefined behavior.
Pin one toolchain (e.g. via
rust-toolchain.toml) and rebuild all crates, libs and executables together with that single version.
Usage
Mode 1: dyn — Rust fat-pointer bridge
Plugin side (the .so/.dylib)
use ;
use Arc;
pub extern "C"
Host side
use DynPlugin;
let plugin = load?;
let transform: &dyn Transform = plugin.trait_ref;
Mode 2: cdyn — COM-style C function table (cross-language)
The plugin exports a function returning a *const to a plain
#[repr(C)] struct of function pointers. No trait objects involved —
callable from C, C++, Zig, anything with a C FFI.
Plugin side (Rust)
use VTablePlugin;
unsafe extern "C"
unsafe extern "C"
pub static MY_PLUGIN_VTABLE: MyVtable = MyVtable ;
pub extern "C"
Host side (Rust)
let plugin = unsafe ;
let vtable = plugin.vtable;
let sum = unsafe ; // 5
The same vtable layout can be produced from any language with a C FFI
(C, C++, Zig, ...): just export a function returning *const MyVtable
to a #[repr(C)]-equivalent struct.
Toolchain / ABI compatibility matrix
The two loading modes have different ABI stability guarantees:
| Mode | Module | ABI stability | Cross-compiler-version safe? | Cross-language (C++/Zig) safe? |
|---|---|---|---|---|
| Rust fat-pointer bridge | dyn_mod |
❌ Unstable — depends on Rust vtable layout | No — host & plugin must use the exact same compiler version | ❌ No (Rust trait objects only) |
| COM-style C function table | cdyn (VTablePlugin<T>) |
✅ Stable — plain #[repr(C)] struct of function pointers |
✅ Yes, to a large extent (layout is fixed by #[repr(C)]) |
✅ Yes — any language with a C FFI |
Verified compiler versions
Cross-version interoperability was verified with an actual host/plugin matrix
test (plugin compiled to a .so with toolchain A, loaded by a host binary
compiled with toolchain B). All 16 combinations passed (both modes) as of
2026-09:
| Toolchain pair (host ↔ plugin) | dyn_mod (fat-pointer bridge) |
cdyn (C function table) |
|---|---|---|
nightly-2025-05-06 ↔ stable 1.98.1 (cross) |
✅ Verified | ✅ Verified |
nightly-2025-05-06 ↔ 1.95.0 (cross) |
✅ Verified | ✅ Verified |
nightly-2025-05-06 ↔ 1.92.0 (cross) |
✅ Verified | ✅ Verified |
stable 1.98.1 ↔ 1.95.0 (cross) |
✅ Verified | ✅ Verified |
stable 1.98.1 ↔ 1.92.0 (cross) |
✅ Verified | ✅ Verified |
1.95.0 ↔ 1.92.0 (cross) |
✅ Verified | ✅ Verified |
| Same-version pairs (all 4 toolchains) | ✅ Verified | ✅ Verified |
| Other / future versions | ❌Undefined behavior — do not rely on it | ⚠️ Usually works, not guaranteed |
Notes:
- The cross-version
dyn_modresults above are an observation, not a guarantee: vtable layout happened to be identical across these four toolchains (spanning nightly-2025-05-06 through stable-1.98.1). Rust officially makes no ABI stability promise between compiler releases — a future release may break it silently. Always re-run the matrix test when adopting a new toolchain, and prefer exact version alignment in production. cdynmode is layout-stable by construction (#[repr(C)]struct of function pointers), but both sides must still compile the sameTdefinition (same field order, same pointer widths).- The
AbiDynFatPtr/AbiStableDynRefstructs are#[repr(C)]and layout-stable across versions; what is not guaranteed stable is the vtable contents behind adyn Traitpointer, which is whydyn_modrequires version alignment.
Safety
Loading dynamic libraries and unpacking raw fat pointers is inherently unsafe.
See the # Safety sections on each API. The retain/release function pointers
give you Arc-like reference counting across the library boundary, but the
caller is still responsible for ABI compatibility (see the warning above).
License
MIT