dyn-loader 0.2.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 **dyn-fat-pointer-bridge** for loading Rust trait objects
from `.so`/`.dylib` plugin files.

- **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.

## ⚠️ 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`)

```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();
```

## 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_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