dyn-loader
Dynamic library loader with dyn-fat-pointer-bridge for loading Rust trait objects
from .so/.dylib plugin files.
- DynLib: wraps
libloading::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: safe, cloneable handle over an
AbiStableDynRef. - DynPlugin: loaded plugin holding a
SafeArcDyn<T>that dereferences to&Tfor calling trait methods on the loaded object.
⚠️ 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
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;
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 — this is what cdyn-loader-sdks (C++/Zig) targets |
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