Skip to content

Repository files navigation

Rust Plugin System

A loadable plugin system for Rust that uses .so shared libraries without requiring #[repr(C)] on data structures. This works by leveraging Rust's native ABI, which is stable when using the same rustc version.

Architecture

The system consists of three main components:

  1. plugin-interface: Defines the Plugin trait that all plugins must implement
  2. example-plugin: An example plugin implementation
  3. host: The host application that loads and uses plugins

Key Features

  • No #[repr(C)] required: Uses Rust's native ABI for trait objects
  • Type-safe: Leverages Rust's trait system for plugin interfaces
  • Dynamic loading: Uses libloading to load .so files at runtime
  • ABI version checking: Automatically verifies that plugins were compiled with the same rustc version as the host

Building

# Build everything
./build.sh

# Or build individually
cargo build -p plugin-interface
cargo build -p example-plugin
cargo build -p host

Running

cargo run -p host

Creating Your Own Plugin

  1. Create a new crate in the workspace:
[package]
name = "my-plugin"
version = "0.1.0"
edition = "2021"

[lib]
name = "my_plugin"
crate-type = ["cdylib"]

[build-dependencies]
rustc_version = "0.4"

[dependencies]
plugin-interface = { path = "../plugin-interface" }
  1. Create a build.rs file to embed the rustc version at compile time:
// build.rs
fn main() {
    let version = rustc_version::version()
        .map(|v| v.to_string())
        .unwrap_or_else(|_| "unknown".to_string());
    
    println!("cargo:rustc-env=RUSTC_VERSION={}", version);
    println!("cargo:rerun-if-changed-env=RUSTC");
}
  1. Implement the Plugin trait:
use plugin_interface::{Plugin, PluginMetadata};

pub struct MyPlugin;

impl Plugin for MyPlugin {
    fn name(&self) -> &str {
        "My Plugin"
    }
    
    fn version(&self) -> &str {
        "1.0.0"
    }
    
    fn execute(&self, input: &str) -> String {
        // Your plugin logic here
        format!("Processed: {}", input)
    }
    
    fn metadata(&self) -> PluginMetadata {
        PluginMetadata {
            name: self.name().to_string(),
            version: self.version().to_string(),
            author: "Your Name".to_string(),
            description: "Plugin description".to_string(),
        }
    }
    
    fn rustc_version(&self) -> &str {
        // Version is embedded at compile time via build.rs
        env!("RUSTC_VERSION")
    }
}

#[no_mangle]
pub extern "Rust" fn create_plugin() -> Box<dyn Plugin> {
    Box::new(MyPlugin)
}

/// Export the rustc version used to compile this plugin
/// This is checked before loading to ensure ABI compatibility
#[no_mangle]
pub extern "Rust" fn plugin_rustc_version() -> &'static str {
    env!("RUSTC_VERSION")
}
  1. Build the plugin:
cargo build -p my-plugin
  1. Load it in the host application by updating the path in host/src/main.rs

Important Notes

  • Same rustc version: Plugins must be compiled with the same rustc version as the host.
  • ABI version checking: The rustc version is embedded in the .so/.dylib metadata and is checked before loading the plugin. If versions don't match, the plugin will fail to load with a clear error message, preventing ABI incompatibility issues at runtime.
  • Version export: All plugins must export a plugin_rustc_version() function (or rupture_rustc_version() for rupture blocks) that returns the rustc version used to compile them. This is automatically generated via build.rs.
  • Library lifetime: The current implementation leaks the library handle. In production, you'd want to manage this more carefully, possibly using Arc or similar
  • ABI stability: While this avoids #[repr(C)], it relies on Rust's native ABI which is not officially stable. However, it works reliably when using the same compiler version
  • Platform differences: On Linux, plugins are built as .so files. On macOS, they're built as .dylib files. The host application handles both automatically. If you specifically need .so files on macOS, you can create a symlink or use a custom build script

Limitations

  • Plugins must be compiled with the same rustc version as the host
  • The library handle is leaked to keep the plugin loaded (can be improved with better lifetime management)
  • No hot-reloading support (would require unloading/reloading libraries)

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages