#smartcontracts

module-registry: Type-Safe Dynamic Plugin System for Rust Applications

Red Asgard releases module-registry, a powerful Rust crate enabling compile-time discovery and runtime instantiation of plugins with type safety guarantees.

Red Asgard Security Team
October 25, 2025
rustpluginsextensibilityarchitectureopen-source

module-registry: Type-Safe Dynamic Plugin System for Rust

Introducing module-registry, Red Asgard's open-source solution for building extensible Rust applications with compile-time plugin discovery and runtime dynamic loading.

Package: module-registry on crates.io
Repository: github.com/redasgard/module-registry
License: MIT
Version: 0.1.0
Downloads: 174+


The Challenge: Extensibility in Rust

Rust's strong type system and compile-time guarantees make it excellent for security-critical applications. However, these same features make dynamic extensibility challenging:

Traditional Problems:

  • Dynamic loading (libloading) bypasses type safety
  • Trait objects have performance overhead
  • Plugin discovery requires boilerplate
  • Version compatibility is manual
  • No standard plugin interface

What We Needed: A plugin system that maintains Rust's safety guarantees while providing the flexibility of dynamic modules?perfect for security tools that need extensible detection engines, custom analyzers, and modular threat intelligence feeds.


Solution: Compile-Time Discovery + Runtime Safety

module-registry provides:

1. Automatic Plugin Discovery

use module_registry::prelude::*;

// Define plugin trait
#[module_interface]
pub trait SecurityScanner {
    fn scan(&self, input: &[u8]) -> ScanResult;
    fn name(&self) -> &str;
    fn version(&self) -> Version;
}

// Plugins auto-register at compile time
#[register_module]
struct MalwareScanner;

#[module_impl]
impl SecurityScanner for MalwareScanner {
    fn scan(&self, input: &[u8]) -> ScanResult {
        // Implementation
    }
    
    fn name(&self) -> &str { "malware-scanner" }
    fn version(&self) -> Version { Version::new(1, 0, 0) }
}

2. Type-Safe Runtime Instantiation

use module_registry::Registry;

fn main() {
    // Registry is populated at compile time
    let registry = Registry::<dyn SecurityScanner>::global();
    
    // List all registered modules
    for module in registry.list() {
        println!("Found: {} v{}", module.name(), module.version());
    }
    
    // Get specific module
    let scanner = registry.get("malware-scanner")?;
    let result = scanner.scan(&suspicious_data);
}

3. Version Management

use module_registry::{Registry, VersionReq};

// Require specific version
let registry = Registry::<dyn SecurityScanner>::global();
let scanner = registry.get_with_version(
    "malware-scanner",
    VersionReq::parse(">=1.0, <2.0")?
)?;

// Or get latest compatible
let latest = registry.get_latest_compatible("malware-scanner")?;

Architecture

Compile-Time Registration

// Macro expansion (simplified):
#[register_module]
struct MyPlugin;

// Expands to:
struct MyPlugin;

#[linkme::distributed_slice(MODULE_REGISTRY)]
static MY_PLUGIN_REGISTRATION: ModuleRegistration = ModuleRegistration {
    name: "MyPlugin",
    version: Version::new(1, 0, 0),
    constructor: || Box::new(MyPlugin),
};

Uses linkme crate for zero-overhead link-time concatenation.

Runtime Discovery

Build Time                Runtime
???????????              ????????????
? Plugin A????           ?          ?
???????????  ?           ? Registry ?
? Plugin B??????????????>?  - List  ?
???????????  ?           ?  - Get   ?
? Plugin C????           ?  - Query ?
???????????              ????????????
   Compile-time              O(1) lookup
   registration              Type-safe access

Real-World Use Cases

1. Security Tool with Multiple Scanners

use module_registry::prelude::*;

// Core application
#[module_interface]
trait VulnerabilityScanner {
    fn scan_code(&self, code: &str) -> Vec<Finding>;
}

// Individual scanner modules (can be in separate crates)

#[register_module]
struct SqlInjectionScanner;

#[module_impl]
impl VulnerabilityScanner for SqlInjectionScanner {
    fn scan_code(&self, code: &str) -> Vec<Finding> {
        // SQL injection detection logic
    }
}

#[register_module]
struct XssScanner;

#[module_impl]
impl VulnerabilityScanner for XssScanner {
    fn scan_code(&self, code: &str) -> Vec<Finding> {
        // XSS detection logic
    }
}

// Main application
fn main() {
    let registry = Registry::<dyn VulnerabilityScanner>::global();
    
    for scanner in registry.iter() {
        let findings = scanner.scan_code(&source_code);
        report.add_findings(findings);
    }
}

2. Threat Intelligence Aggregation

#[module_interface]
trait ThreatFeed {
    fn fetch_indicators(&self) -> Vec<IoC>;
    fn source_name(&self) -> &str;
}

#[register_module]
struct AbuseIpDbFeed;

#[register_module]
struct MispFeed;

#[register_module]
struct CustomIntelFeed;

// Automatically discovers and uses all feeds
fn aggregate_threats() -> Vec<IoC> {
    let registry = Registry::<dyn ThreatFeed>::global();
    
    registry.iter()
        .flat_map(|feed| feed.fetch_indicators())
        .collect()
}

3. Blockchain Protocol Analyzers

#[module_interface]
trait ProtocolAnalyzer {
    fn analyze_transaction(&self, tx: &Transaction) -> Analysis;
    fn supported_protocol(&self) -> &str;
}

#[register_module]
struct UniswapAnalyzer;

#[register_module]
struct AaveAnalyzer;

#[register_module]
struct BalancerAnalyzer;

// Dynamic protocol detection
fn analyze(tx: &Transaction) -> Analysis {
    let registry = Registry::<dyn ProtocolAnalyzer>::global();
    
    for analyzer in registry.iter() {
        if tx.to == analyzer.supported_protocol() {
            return analyzer.analyze_transaction(tx);
        }
    }
    
    Analysis::Unknown
}

Advanced Features

Conditional Compilation

#[register_module]
#[cfg(feature = "enterprise")]
struct EnterpriseScanner;

// Only registered if "enterprise" feature enabled

Lazy Initialization

#[register_module(lazy)]
struct HeavyPlugin {
    model: OnceCell<MLModel>,
}

impl HeavyPlugin {
    fn load_model(&self) -> &MLModel {
        self.model.get_or_init(|| {
            // Expensive initialization only when first used
            MLModel::load_from_disk()
        })
    }
}

Dependency Injection

#[module_interface]
trait Scanner {
    fn scan(&self, deps: &Dependencies) -> Result;
}

struct Dependencies {
    db: Database,
    config: Config,
    logger: Logger,
}

// Plugins receive dependencies
let scanner = registry.get("my-scanner")?;
scanner.scan(&deps)?;

Performance Characteristics

Overhead:

  • Compile-time registration: Zero runtime cost
  • Module lookup: O(1) hash map
  • Module instantiation: Lazy, on-demand
  • Memory: ~64 bytes per registered module

Benchmarks:

Register 100 modules:      0ms (compile-time)
List all modules:          0.01ms
Get module by name:        0.001ms (hash lookup)
Instantiate module:        Varies by module

Safety Guarantees

Type Safety:

  • Compile-time trait checking
  • No unsafe casting
  • Version compatibility verified

Memory Safety:

  • No manual memory management
  • RAII for cleanup
  • No dangling references

Thread Safety:

  • Send + Sync where appropriate
  • Interior mutability properly scoped
  • No data races

Installation

[dependencies]
module-registry = "0.1"

# With tracing support
module-registry = { version = "0.1", features = ["tracing"] }
cargo add module-registry

Why We Built This

At Red Asgard, we build security tools that need to be:

  • Extensible - New detection modules added easily
  • Type-safe - No runtime surprises
  • Performant - Security checks can't slow down production
  • Maintainable - Clean separation of concerns

We tried existing plugin systems but found them either too unsafe (dynamic loading) or too rigid (compile-time only). module-registry strikes the right balance.


Comparison

Featuremodule-registryabi_stablelinkmedlopen
Type safetyStrongMediumStrongWeak
Dynamic loadingCompile-timeRuntimeLink-timeRuntime
Zero overheadYesNoYesNo
Cross-platformYesLimitedYesYes
Ease of useHighMediumLowLow

Get Started

Basic example:

use module_registry::prelude::*;

#[module_interface]
trait Processor {
    fn process(&self, data: &str) -> String;
}

#[register_module]
struct UpperCaseProcessor;

#[module_impl]
impl Processor for UpperCaseProcessor {
    fn process(&self, data: &str) -> String {
        data.to_uppercase()
    }
}

fn main() {
    let registry = Registry::<dyn Processor>::global();
    let processor = registry.get("UpperCaseProcessor").unwrap();
    
    println!("{}", processor.process("hello")); // "HELLO"
}

See more examples: GitHub Repository


Released: October 24, 2025
Version: 0.1.0
Perfect for: Extensible security tools, plugin architectures, modular applications

Share this article

Help spread the word about security best practices.

Need Security Help?

Our team can help secure your blockchain, web applications, and infrastructure.