Course contentsShow
← Rust
Lesson 174 of 1,9102. The Ownership ModelFree lesson

Ownership in Plugin Architectures

Handle dynamic library loading while maintaining strict boundary ownership.

In this lesson, you will learn how to maintain Rust’s strict ownership guarantees when passing data across the boundaries of dynamically loaded plugins.

The Boundary Problem

When you load a dynamic library (a .so or .dll file) into your main application, you are effectively bridging two potentially different memory management contexts. If your plugin allocates memory, the main application doesn't "own" that memory. If you try to drop a Rust object allocated in the plugin from the main application's side, you will trigger a catastrophic crash because the two entities may be using different allocators or differing binary layouts.

To maintain ownership safety, the golden rule is: Whoever allocates the memory must be the one to free it.

Establishing Ownership via Interfaces

To handle this, we define a common interface (a Trait) that the plugin implements. When we pass data to a plugin, we must decide if we are moving ownership or borrowing. Since the plugin lives in a dynamic library, the boundary must be kept clean by using pointers or opaque handles rather than moving complex Rust types directly across the FFI (Foreign Function Interface) boundary.

Consider this pattern:

// Defined in a shared crate used by both host and plugin
pub trait Plugin {
    // The plugin takes ownership of the raw data, processes it, 
    // and returns a result, ensuring the plugin-side allocator manages the cleanup.
    fn process(&self, data: *mut u8, len: usize) -> Result<(), PluginError>;
}

By passing a raw pointer (*mut u8) and a length, we create a clear ownership hand-off. The plugin acknowledges that it is responsible for the memory at that address. To prevent memory leaks, we often provide a "deallocation" function that the host calls when it is finally done with the data:

#[no_mangle]
pub extern "C" fn free_data(ptr: *mut u8) {
    unsafe {
        // The plugin-side allocator is used to free the memory it created.
        let _ = Box::from_raw(ptr);
    }
}

This keeps the ownership invariant intact: the host never touches the underlying heap allocation directly, and the plugin always cleans up its own mess, even though the host triggered the request.

Key Takeaway: Never attempt to drop an object across a dynamic library boundary; always provide an explicit "cleanup" function that allows the library that allocated the memory to be the one to free it.