dyn-loader 0.4.0

Dynamic library loader with dyn-fat-pointer-bridge for loading trait objects from .so/.dylib plugins. IMPORTANT: strictly align the Rust compiler version across all libs and executables to guarantee ABI compatibility.
Documentation
# dyn-loader

Dynamic library loader with **two plugin-loading modes**:

1. **`dyn` mode** — Rust fat-pointer bridge: load Rust trait objects from
   `.so`/`.dylib` plugin files, with Arc-like retain/release.
2. **`cdyn` mode** — COM-style C function tables (`VTablePlugin<T>`): plain
   `#[repr(C)]` function-pointer structs, ABI-stable, usable from C/C++/Zig.

Core types:

- **`DynLib`**: wraps `libloading::Library` with `Arc` for 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 an `AbiStableDynRef`.
- **`DynPlugin<T>`**: loaded plugin holding a `SafeArcDyn<T>` that dereferences
  to `&T` for 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`)**

```rust,ignore
use dyn_loader::{AbiStableDynRef, SafeArcDyn};
use std::sync::Arc;

#[no_mangle]
pub extern "C" fn core_ast_transform_entry() -> AbiStableDynRef {
    SafeArcDyn::from_arc(Arc::new(MyTransform) as Arc<dyn Transform>).into_abi()
}
```

**Host side**

```rust,ignore
use dyn_loader::DynPlugin;

let plugin = DynPlugin::<dyn Transform>::load(
    "libmy_transform.so",
    b"core_ast_transform_entry\0",
)?;
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)**

```rust,ignore
use dyn_loader::VTablePlugin;

#[repr(C)]
pub struct MyVtable {
    pub add: unsafe extern "C" fn(i32, i32) -> i32,
    pub name: unsafe extern "C" fn() -> *const std::os::raw::c_char,
}

unsafe extern "C" fn add(a: i32, b: i32) -> i32 { a + b }
unsafe extern "C" fn name() -> *const std::os::raw::c_char {
    c"my_plugin".as_ptr()
}

#[no_mangle]
pub static MY_PLUGIN_VTABLE: MyVtable = MyVtable { add, name };

#[no_mangle]
pub extern "C" fn my_plugin_get_vtable() -> *const MyVtable {
    &MY_PLUGIN_VTABLE
}
```

**Host side (Rust)**

```rust,ignore
let plugin = unsafe {
    VTablePlugin::<MyVtable>::load("libmy_plugin.so", b"my_plugin_get_vtable\0")?
};
let vtable = plugin.vtable();
let sum = unsafe { (vtable.add)(2, 3) }; // 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_mod` results 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.
- `cdyn` mode is layout-stable by construction (`#[repr(C)]` struct of
  function pointers), but both sides must still compile the *same* `T`
  definition (same field order, same pointer widths).
- The `AbiDynFatPtr` / `AbiStableDynRef` structs are `#[repr(C)]` and
  layout-stable across versions; what is *not* guaranteed stable is the
  **vtable contents** behind a `dyn Trait` pointer, which is why `dyn_mod`
  requires 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