Skip to main content

geoff_plugin/
loader.rs

1use std::path::Path;
2
3use libloading::{Library, Symbol};
4
5use crate::traits::Plugin;
6
7/// A Rust plugin loaded from a cdylib shared library.
8///
9/// The shared library must export a `create_plugin` function with the signature:
10/// `extern "C" fn() -> *mut dyn Plugin`
11pub struct RustPluginLoader {
12    /// Loaded libraries kept alive for the duration of the plugin's lifetime.
13    _libraries: Vec<Library>,
14    /// Plugins created from loaded libraries.
15    plugins: Vec<Box<dyn Plugin>>,
16}
17
18impl RustPluginLoader {
19    pub fn new() -> Self {
20        Self {
21            _libraries: Vec::new(),
22            plugins: Vec::new(),
23        }
24    }
25
26    /// Load a Rust plugin from a cdylib shared library at the given path.
27    ///
28    /// The library must export: `extern "C" fn create_plugin() -> *mut dyn Plugin`
29    ///
30    /// # Safety
31    ///
32    /// This function loads and executes arbitrary code from a shared library.
33    /// The caller must ensure the library is trusted and compatible.
34    pub unsafe fn load(
35        &mut self,
36        path: &Path,
37    ) -> std::result::Result<&dyn Plugin, Box<dyn std::error::Error + Send + Sync>> {
38        // SAFETY: caller guarantees the library is trusted
39        let lib = unsafe { Library::new(path) }.map_err(|e| {
40            Box::new(std::io::Error::other(format!(
41                "failed to load plugin library {}: {e}",
42                path.display()
43            )))
44        })?;
45
46        // SAFETY: caller guarantees the library exports a compatible create_plugin symbol
47        let constructor: Symbol<unsafe extern "C" fn() -> *mut dyn Plugin> =
48            unsafe { lib.get(b"create_plugin") }.map_err(|e| {
49                Box::new(std::io::Error::other(format!(
50                    "plugin library {} missing create_plugin symbol: {e}",
51                    path.display()
52                )))
53            })?;
54
55        let raw = unsafe { constructor() };
56        if raw.is_null() {
57            return Err(Box::new(std::io::Error::other(format!(
58                "create_plugin returned null in {}",
59                path.display()
60            ))));
61        }
62        let plugin = unsafe { Box::from_raw(raw) };
63
64        self.plugins.push(plugin);
65        self._libraries.push(lib);
66
67        // Return a reference to the last inserted plugin
68        Ok(self.plugins.last().unwrap().as_ref())
69    }
70
71    /// Returns references to all loaded plugins.
72    pub fn plugins(&self) -> &[Box<dyn Plugin>] {
73        &self.plugins
74    }
75
76    /// Consume the loader and return all loaded plugins.
77    pub fn into_plugins(self) -> Vec<Box<dyn Plugin>> {
78        self.plugins
79    }
80}
81
82impl Default for RustPluginLoader {
83    fn default() -> Self {
84        Self::new()
85    }
86}