# How pgrust Manages Dynamic Library Loading for PostgreSQL Extensions

> Discover how pgrust manages dynamic library loading for PostgreSQL extensions in Rust. Learn about its OS-edge loader and in-process registry for efficient symbol resolution without dlopen.

- Repository: [Michael Malis/pgrust](https://github.com/malisper/pgrust)
- Tags: internals
- Published: 2026-07-13

---

**pgrust implements PostgreSQL’s dynamic library loader entirely in Rust using two distinct pathways: an OS‑edge loader that gracefully fails for unported C extensions, and an in‑process registry that resolves symbols for libraries ported to Rust, eliminating the need for `dlopen` while maintaining PostgreSQL‑compatible error handling.**

pgrust is a Rust implementation of the PostgreSQL backend that replicates the server’s extension loading mechanism without exposing a C ABI. Unlike traditional PostgreSQL backends that rely on `dlopen` and `dlsym` to load `.so` files, pgrust handles dynamic library loading for PostgreSQL extensions through a hybrid seam architecture that keeps the runtime entirely in safe Rust.

## The OS-Edge Loader: Handling Unported Libraries

When pgrust encounters a request for a library that has not been ported from C to Rust, it delegates to the **dynloader** crate. This crate provides OS‑edge seams that wrap POSIX system calls but deliberately prevent actual dynamic loading to preserve the no‑C‑ABI invariant.

### Filesystem Detection with stat_identity

The loader performs real filesystem checks to determine if a library file exists and identify its inode. In [`crates/port/dynloader/src/lib.rs`](https://github.com/malisper/pgrust/blob/main/crates/port/dynloader/src/lib.rs) (lines 40‑79), the `stat_identity` seam wraps the POSIX `stat(2)` syscall to support “same‑inode” detection, allowing pgrust to verify file identity without loading the library.

### Graceful Failure for Missing C Extensions

All dynamic loading operations in the OS‑edge pathway are designed to fail gracefully. The `open_library` seam in [`crates/port/dynloader/src/lib.rs`](https://github.com/malisper/pgrust/blob/main/crates/port/dynloader/src/lib.rs) (lines 82‑107) aborts with a PostgreSQL‑style `ereport(ERROR)` message before attempting any actual `dlopen` operation. Other seams—including `call_pg_init`, `function_exists`, `fetch_finfo_record`, `plugin_init`, and `invoke_output_plugin_callback`—are stubbed to return deterministic errors via `no_c_abi_error` and panic only if reached, which should never occur because `open_library` terminates execution first.

This behavior mirrors PostgreSQL’s real failure mode for missing libraries while keeping the Rust backend alive rather than causing an uncontrolled panic.

## The In-Process Registry: Rust-Native Extensions

Extensions whose C bodies have been ported to Rust—such as `plpgsql`, `test_decoding`, and `regress`—bypass the OS loader entirely by registering themselves in a shared registry maintained by the **dfmgr_seams** crate.

### Registering Ported Libraries

Each ported crate calls `register_builtin_library` from its `init_seams()` function to insert a `BuiltinLibraryEntry` into the registry. This entry contains the library name, a lookup function for resolving symbols, and an optional `_PG_init`‑equivalent initialization routine.

```rust
// Example: Register a ported library (e.g. plpgsql) in its init_seams()
use pgrust::dfmgr_seams::register_builtin_library;
use pgrust::dfmgr_seams::BuiltinLibraryEntry;
use pgrust::fmgr::LoadedExternalFunc;

fn plpgsql_lookup(symbol: &str) -> Option<LoadedExternalFunc> {
    // map “function_name” → LoadedExternalFunc
    // (implementation omitted for brevity)
    unimplemented!()
}

pub fn init_seams() {
    register_builtin_library(BuiltinLibraryEntry {
        name: "plpgsql",
        lookup: plpgsql_lookup,
        pg_init: Some(plpgsql_pg_init), // optional init routine
    });
}

```

### Symbol Resolution via the Registry

When the backend needs to load a function, the **dfmgr_seams** crate queries the registry before falling back to the OS‑edge loader. In [`crates/backend/utils/fmgr/dfmgr_seams/src/lib.rs`](https://github.com/malisper/pgrust/blob/main/crates/backend/utils/fmgr/dfmgr_seams/src/lib.rs) (lines 24‑31, 38‑45, 61‑71), the functions `builtin_library_present` and `resolve_builtin_library_function` check whether a requested library is already known. If found, the loader returns the Rust implementation directly without invoking `dlopen`.

```rust
// Example: Loading a function – the backend first checks the registry
fn load_external_function(probin: &str, prosrc: &str, oid: Oid) -> PgResult<LoadedExternalFunc> {
    // Try the builtin registry first
    if let Some(func) = pgrust::dfmgr_seams::resolve_builtin_library_function(probin, prosrc)? {
        return Ok(func);
    }

    // Fallback to the OS‑edge loader (will always error in the Rust build)
    pgrust::dynloader_seams::open_library::call(probin)?;
    // unreachable – the above always returns an error
}

```

## Integration with the Function Manager

The **dfmgr** crate ([`crates/backend/utils/fmgr/dfmgr/src/lib.rs`](https://github.com/malisper/pgrust/blob/main/crates/backend/utils/fmgr/dfmgr/src/lib.rs)) serves as the integration layer that delegates to either the OS‑edge loader or the builtin registry. This architecture allows pgrust to maintain API compatibility with PostgreSQL’s dynamic library loading interface while internally routing requests through safe Rust code paths.

## Summary

- **OS‑edge loader** (`dynloader` crate): Wraps `stat(2)` for filesystem checks but forces graceful errors for any attempt to `dlopen` unported C libraries.
- **In‑process registry** (`dfmgr_seams` crate): Stores `BuiltinLibraryEntry` structs for ported extensions, enabling direct symbol resolution without dynamic loading.
- **Graceful degradation**: Unported extensions trigger PostgreSQL‑style error reports rather than crashes, maintaining stability.
- **Safe Rust implementation**: All loading logic avoids `unsafe` blocks and C FFI, relying instead on Rust function pointers and registry lookups.

## Frequently Asked Questions

### How does pgrust handle extensions that haven’t been ported to Rust?

When pgrust encounters an unported extension, the `open_library` seam in [`crates/port/dynloader/src/lib.rs`](https://github.com/malisper/pgrust/blob/main/crates/port/dynloader/src/lib.rs) aborts with an `ereport(ERROR)` message before attempting any system `dlopen` call. This mirrors PostgreSQL’s error behavior for missing libraries while preventing the Rust backend from attempting to load C ABI code.

### Can pgrust load actual `.so` files at runtime?

No. The Rust backend does not expose a C ABI, so `open_library` always fails gracefully with a descriptive error. Actual dynamic library loading is only possible for extensions that have been fully ported to Rust and registered via `register_builtin_library`.

### Where does the registry store ported library information?

The registry is maintained in the **dfmgr_seams** crate ([`crates/backend/utils/fmgr/dfmgr_seams/src/lib.rs`](https://github.com/malisper/pgrust/blob/main/crates/backend/utils/fmgr/dfmgr_seams/src/lib.rs)). Ported crates like `plpgsql` and `test_decoding` call `register_builtin_library` during their initialization to insert `BuiltinLibraryEntry` structs containing symbol lookup functions and optional initialization callbacks.

### What happens if `stat_identity` finds the library file but it isn’t in the registry?

The `stat_identity` seam confirms the file exists on disk, but the loading process still proceeds through `open_library`, which will return an error because the library is not registered as a builtin. This two‑phase check ensures PostgreSQL‑compatible error messages while maintaining the safety invariant that no C code is executed.