Skip to content

CandleVM Plugins ​

A CandleVM plugin is a Rust dynamic library that provides named modules and functions to Candle programs. The executable declares the required plugin name and version in its plugin dependency table. At load time, CandleVM loads the library, checks its Candle version, and matches the plugin's name and version against that requirement.

Plugins are loaded from paths passed with the plugin option, or automatically from the plugins directory next to the CandleVM executable. The required library extension depends on the operating system; the CLI reference lists them.

Install a plugin ​

To install a CandleVM plugin, place its dynamic library in the plugins directory next to the CandleVM executable. If the directory does not exist, create it.

For example, a typical installation may look like this:

text
candlevm-v1.0.1-windows-x86_64/
├── candlevm
└── plugins/
    └── myplugin-v1.0.0-windows-x86_64.dll

The library extension depends on the operating system. Make sure to use a plugin build that is compatible with your operating system and CPU architecture.

You must also ensure that the plugin supports the same Candle ecosystem version as your CandleVM executable. CandleVM checks this version when loading the plugin and rejects incompatible plugins. The plugin's name and version must also match the dependency declared by the executable.

Once installed, CandleVM can automatically discover plugins in the plugins directory. Alternatively, you can load a plugin from another location using the plugin option.

Create a plugin crate ​

Add CandleVM as a dependency and build the plugin as a dynamic library. Add candlevm to your Cargo.toml using its published version on crates.io:

toml
[package]
name = "example_plugin"
version = "1.0.0"
edition = "2024"

[lib]
crate-type = ["dylib"]

[dependencies]
candlevm = "1"

⚠ Make sure to use the recommended Rust compiler version when building plugins. Using a different version may cause compatibility issues with CandleVM. See the recommended Rust compiler version for details.

Export the plugin entry points ​

Every plugin library must export these two functions with the exact names and signatures:

rust
use candlevm::constants::CANDLE_VERSION;
use candlevm::plugin::Plugin;

#[unsafe(no_mangle)]
pub extern "Rust" fn candle_version() -> u16 {
    CANDLE_VERSION
}

#[unsafe(no_mangle)]
pub extern "Rust" fn candle_plugin_init() -> Plugin {
    create_plugin()
}

candle_version returns the Candle ecosystem version supported by the plugin; the current value is 1. CandleVM rejects the plugin if this value does not match its own Candle version. candle_plugin_init returns a [Plugin] containing the plugin's name, its version string, and its modules. The plugin name and plugin version must match the executable's dependency entry exactly.

The interface uses the Rust ABI, which has no stability guarantees. Build the plugin for a compatible target and Rust toolchain, and against a compatible CandleVM crate. The loader checks the Candle ecosystem version, but it cannot verify Rust ABI or toolchain compatibility. The Rust Reference on external ABIs describes this stability limitation.

Plugin, module, and function definitions ​

The public API is in candlevm::plugin:

  • Plugin::new(name, version, modules) creates a plugin. modules is a Vec<PluginModule>.
  • PluginModule::new(name, functions) creates a named module. functions is a Vec<PluginFunction>.
  • PluginFunction::new(name, parameter_count, handler) creates a callable function. Its parameter count is a u8; CandleVM accepts up to 8 and rejects a plugin that declares more.

Module and function names are the names Candle programs use with LoadModule and LoadFunction. Module names must be unique across the executable and loaded plugins; function names must be unique within a module. The loader rejects duplicate plugin names and name conflicts.

Plugin function signature ​

The handler accepted by PluginFunction::new has this signature:

rust
Arc<dyn Fn(&mut Vm, &[Value]) -> CandleResult<Value>>

A handler can be an ordinary Rust function with the compatible function signature fn(&mut Vm, &[Value]) -> CandleResult<Value>, wrapped in Arc::new.

The Vm argument gives the handler access to the running virtual machine and its registers, globals, and heap; the slice contains its arguments as Values. The returned Value becomes the function's result. Returning Err(CandleError) raises a CandleVM error, which can be caught by Pcall.

The slice contains exactly the declared number of parameters. If a call supplies fewer arguments, CandleVM fills missing parameters with Null; extra arguments are consumed and ignored. A plugin can validate argument types with the methods on Value and return an error for invalid input.

For example, this function adds two numeric arguments:

rust
use candlevm::error::CandleResult;
use candlevm::value::Value;
use candlevm::vm::Vm;

fn add(_: &mut Vm, arguments: &[Value]) -> CandleResult<Value> {
    let left = arguments[0].as_f64()?;
    let right = arguments[1].as_f64()?;
    Value::from_f64(left + right)
}

The callback and module can be assembled by create_plugin, which is called by the exported candle_plugin_init entry point:

rust
use candlevm::plugin::{PluginFunction, PluginModule};
use std::sync::Arc;

fn create_plugin() -> Plugin {
    Plugin::new(
        "example".into(),
        "1.0.0".into(),
        vec![PluginModule::new(
            "example.math".into(),
            vec![PluginFunction::new("add".into(), 2, Arc::new(add))],
        )],
    )
}

The plugin name and version must match the executable's dependency entry. Function IDs follow their order in the module's function list.

Built for the Candle ecosystem · Powered by VitePress.