Course contentsShow
← Rust
Lesson 1661 of 1,91014. System Integration and EcosystemFree lesson

Dynamic Library Loading

You will load and execute shared object code at runtime with runtime symbol resolution.

In this lesson, you will learn how to interface with shared object files (.so, .dylib, or .dll) at runtime, allowing your Rust application to extend its functionality without requiring a full recompilation.

The Mechanics of Runtime Loading

While we typically rely on Rust’s static linking to resolve dependencies at compile time, there are scenarios—such as building a plugin system or modular application—where you need to defer this until the program is already executing.

In Rust, the libloading crate is the standard abstraction for this process. It acts as a wrapper around the platform-specific APIs (dlopen on Unix/macOS, LoadLibrary on Windows). The process follows a strict lifecycle:

  1. Load: The library file is mapped into the process's memory space.
  2. Resolve: You search for a specific symbol (function or variable) by its string name.
  3. Cast: You cast the raw pointer to a safe Rust function signature.
  4. Execute: You invoke the function as you would any other Rust closure.

Practical Implementation

Consider a library that exports a function double(n: i32) -> i32. Loading it looks like this:

use libloading::{Library, Symbol};

unsafe {
    // 1. Load the shared object
    let lib = Library::new("path/to/my_plugin.so").expect("Failed to load lib");

    // 2. Resolve the function symbol by name
    let func: Symbol<unsafe extern "C" fn(i32) -> i32> = lib.get(b"double")
        .expect("Failed to find symbol");

    // 3. Execute
    let result = func(21);
    println!("The result is: {}", result);
}

Critical Safety Considerations

Because dynamic loading bypasses Rust’s compile-time type checks, you are operating in a unsafe context. You must guarantee that:

  • Binary Compatibility: The function signature in your Rust code exactly matches the calling convention (extern "C") and the data types expected by the shared object.
  • Lifetime Management: The Library object owns the resources. If the Library goes out of scope and is dropped, the symbols it provided become dangling pointers, leading to immediate segmentation faults if accessed again. Always keep the Library handle alive for the duration of the plugin's utility.

Key Takeaway: Dynamic library loading enables runtime extensibility but shifts the burden of type safety and memory management from the compiler to you, requiring rigorous validation of symbol signatures and strict lifecycle control of the library handle.