Pumpkin-MC/Pumpkin · error

PLUGIN must be initialized with register_plugin before use

Error message

PLUGIN must be initialized with register_plugin before use

What it means

`plugin()` returns the global singleton plugin instance stored in a `OnceLock`, which is only populated by `register_plugin`. If `plugin()` is called before registration, `OnceLock::get()` returns `None` and the `.expect` panics with this message. It protects against using plugin services (logging, config access) before the runtime has finished constructing the plugin.

Solutions

  1. Move the `plugin()` call into plugin methods that only run after registration (event handlers, `on_load`).
  2. Ensure `register_plugin!` (or `register_plugin`) runs in the crate's designated entry point exactly as the plugin template shows.
  3. Replace eager static initialization with lazy per-call access inside handlers.
  4. If you need pre-registration state, capture it in your own globals rather than via `plugin()`.

Example fix

// before
static STATE: LazyLock<String> = LazyLock::new(|| plugin().name().to_string()); // panics at load
// after
fn state() -> String {
    plugin().name().to_string() // called from within event handlers
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Guard before calling helpers that use plugin():
fn plugin_available() -> bool { PLUGIN.get().is_some() } // or defer all access to post-registration hooks

Type guard

fn try_plugin() -> Option<&'static dyn Plugin> { PLUGIN.get().map(Box::as_ref) }

Try / catch

// Rust panic is not catchable idiomatically; use the safe accessor:
if let Some(p) = try_plugin() { /* use p */ } else { /* defer until registered */ }

Prevention

When it happens

Trigger: Calling `pumpkin_plugin_api::plugin()` (directly or via helpers like logging/config accessors) from a `#[no_setup]`/static initializer, a `static`/`lazy` evaluated at load time, or any code path executed before the host calls the plugin's setup/register entry point.

Common situations: Logging during DLL/SO load (`static` initializers or `ctor`-style hooks); spawning a thread in a static initializer that calls `plugin()`; using a `OnceLock`/`LazyLock` of derived state that reads the plugin at module init; calling plugin helpers from `extern "C"` callbacks invoked before registration.

Related errors


AI-assisted analysis of Pumpkin-MC/Pumpkin@8d4639e25a (2026-09-09). Data as JSON: /api/errors/8a6016037fd0b225. Report an issue: GitHub.

Appendix: source

Thrown at crates/pumpkin-plugin-api/src/lib.rs:494

#[doc(hidden)]
pub fn register_plugin(build_plugin: fn() -> Box<dyn Plugin>) {
    let _ = tracing::subscriber::set_global_default(WitSubscriber::new());
    assert!(
        PLUGIN.set(build_plugin()).is_ok(),
        "register_plugin must only be called once"
    );
}

/// Returns a reference to the currently loaded plugin instance.
///
/// # Panics
/// If called before [`register_plugin`] has initialized `PLUGIN`.
fn plugin() -> &'static dyn Plugin {
    #[allow(clippy::expect_used)]
    PLUGIN
        .get()
        .map(Box::as_ref)
        .expect("PLUGIN must be initialized with register_plugin before use")
}

/// The singleton plugin instance, initialised by [`register_plugin`].
static PLUGIN: OnceLock<Box<dyn Plugin>> = OnceLock::new();

/// Registers the provided type as a Pumpkin plugin.
///
/// This macro generates the WebAssembly export entry point that the server uses to
/// instantiate the plugin. The type must implement the [`Plugin`] trait.
///
/// # Example
/// ```rust,ignore
/// register_plugin!(MyPlugin);
/// ```
#[macro_export]
macro_rules! register_plugin {
    ($plugin_type:ty) => {
        #[unsafe(export_name = "init-plugin")]

View on GitHub (pinned to 8d4639e25a)