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.
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
| Feature | module-registry | abi_stable | linkme | dlopen |
|---|---|---|---|---|
| Type safety | Strong | Medium | Strong | Weak |
| Dynamic loading | Compile-time | Runtime | Link-time | Runtime |
| Zero overhead | Yes | No | Yes | No |
| Cross-platform | Yes | Limited | Yes | Yes |
| Ease of use | High | Medium | Low | Low |
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.
Related Articles
Balancer V2 Vault Exploit: Forensic Analysis of $117M Breach
Preliminary forensic analysis of the Nov 3, 2025 Balancer V2 exploit with on-chain verification and a per-chain verification plan. Official disclosure pending.
threat-intel: Comprehensive Threat Intelligence Framework for Rust
Red Asgard's threat-intel provides multi-source threat aggregation, CVE integration, and automated risk assessment for security operations teams.
Need Security Help?
Our team can help secure your blockchain, web applications, and infrastructure.