Tuesday, September 08, 2026

Rust dynamic code generation and loading

Rust can achieve dynamic code generation and loading, but because it has no managed runtime or stable ABI, you must implement it through specific workarounds. [1, 2, 3, 4, 5]

Because Rust compiles straight to machine code, you cannot simply eval a string of code at runtime natively. Instead, developers use four primary architectures to generate and load code dynamically: [1, 2, 3, 4]
1. WebAssembly (WASM) Runtimes (Recommended)
You can compile dynamically generated code into a .wasm file and run it inside your Rust application. This is the safest, most popular method for creating flexible plugin architectures. [1, 2, 3, 4]
  • How it works: Embed a sandboxed WASM virtual machine into your host application.
  • Key crates: Use Wasmtime or Wasmer to interpret or JIT-compile the WASM binary on the fly.
  • Pros: Completely memory-safe, fully isolated sandboxing, and works cross-platform.
  • Cons: Introduces minor overhead when passing data across the sandbox boundary. [1, 2, 3, 4, 5]
2. Native Dynamic Libraries (.dylib, .so, .dll)
You can use Rust to programmatically invoke a compiler to generate a shared library file and load it using OS-level APIs. [1, 2, 3]
  • How it works: Your application writes code to a file, runs rustc or cargo via std::process::Command, and loads the output library.
  • Key crates: Use the libloading crate to handle dlopen / dlsym bindings across platforms safely.
  • Pros: Native execution speed with zero virtualization overhead.
  • Cons: Requires a compiler installed on the host system. It is inherently unsafe, risks memory crashes, and requires strict adherence to the stable extern "C" ABI to prevent compatibility breakage. [1, 2, 3, 4, 5]
3. Just-In-Time (JIT) Compilation
You can generate machine code instructions directly inside an allocated memory buffer and jump to that code execution path. [1, 2, 3]
  • How it works: Allocate memory, mark it executable (mprotect / VirtualProtect), emit raw byte opcodes into it, and cast it to a function pointer.
  • Key crates: Use Cranelift (the code generator behind Wasmtime) or dynasm-rs for a more ergonomic runtime assembler.
  • Pros: Bypasses the filesystem entirely; fastest possible code generation loop.
  • Cons: Highly architecture-specific (X86 vs ARM), complex to implement safely, and requires auditing for security vulnerabilities like arbitrary code execution. [1, 2, 3, 4, 5]
4. Embedded Scripting Engines
If your code generation does not strictly require Rust syntax, you can embed a lightweight language engine. [1]
  • How it works: Generate text strings of a scripting language and feed them into a runtime engine hosted inside your application.
  • Key crates: Use Rhai (a native Rust-like scripting language) or mlua (high-performance Lua bindings).
  • Pros: Simple to set up, highly performant, and completely safe from segmentation faults.
  • Cons: You are no longer writing or generating pure Rust code. [1, 2, 3, 4, 5]
To help me narrow down the best approach for you, can you tell me:
  • What kind of code are you looking to generate? (e.g., math formulas, dynamic plugin logic, database queries)
  • Does the generated code need to be written in pure Rust syntax, or is a scripting language acceptable?
  • What are your target security constraints? (e.g., will you run untrusted user input?)




This article, titled Dynamic Library Loading in Rust and Plugin Management, outlines how to implement a plugin system in Rust by dynamically loading shared libraries at runtime using FFI (Foreign Function Interface).


  1. Dynamic Loading Library (libloading)

    • Uses the libloading crate to dynamically load shared objects (.so or .dll files) at runtime using OS-level APIs (e.g., GetProcAddress on Windows).

  2. Defining Traits and ABI Interfaces

    • Since standard C ABI dynamic linkage supports limited types, the plugin architecture defines a common Rust trait (UcenterApp).

    • Dynamic libraries expose an extern "C" creator function (_app_extend_create) that allocates an instance of the trait on the heap using Box::new(...) and returns a raw pointer (Box::into_raw(...)).

  3. Cargo Configuration

    • Dynamic library crates must set crate-type = ["cdylib", "rlib"] in Cargo.toml so the compiler generates dynamic link libraries instead of static ones.

  4. Plugin Manager Architecture

    • An AppExtendManager maintains loaded Library instances to prevent premature unloading and stores plugin implementations in a map like HashMap<String, Arc<Box<dyn Trait>>>.

    • It unsafe-calls the plugin creator symbol, reconverts the raw pointer back into a Rust-managed Box via Box::from_raw(...), runs setup callbacks (like on_extend_load), and tracks instances safely across threads using Arc.

  5. Simplification with Macros

    • Demonstrates a custom declare_app_extend! macro to eliminate boiler-plate code when defining and exporting plugin constructor functions.





No comments: