Appearance
Rust Guide
Build Noorle plugins with Rust.
Why Rust?
Strengths
- Zero-Cost Abstractions - High-level code compiles to efficient WASM without runtime overhead
- Memory Safety - Prevent buffer overflows and memory bugs at compile time
- Small Binaries - Excellent WASM optimization and dead code elimination
- Mature Toolchain - First-class
wasm32-wasip2
target with robust tooling - Type Safety - Strong typing prevents runtime errors
Best For
- High-performance plugins
- Security-critical applications
- Resource-constrained environments
- Complex data processing
- Systems integration
Trade-offs
- Longer compilation times
- Steeper learning curve for beginners
- More verbose than dynamic languages
Quick Start
Prerequisites
bash
# Install Noorle CLI
curl -L cli.noorle.dev | sh
Create Plugin
bash
# Initialize from template
noorle plugin init my-plugin --template rust
cd my-plugin
# Install dependencies (Rust toolchain, wasm32-wasip2 target, wasm tools)
noorle plugin prepare
# Build and deploy
noorle plugin publish
Minimal Example
rust
wit_bindgen::generate!({
world: "my-plugin-component",
path: "./wit",
});
struct MyPlugin;
impl Guest for MyPlugin {
fn process(input: String) -> Result<String, String> {
Ok(format!("Processed: {}", input))
}
}
export!(MyPlugin);
Project Structure
tree
my-plugin/
├── src/lib.rs # Main implementation
├── wit/world.wit # API definition
├── Cargo.toml # Rust dependencies
├── build.sh # Build script
├── prepare.sh # Dependency installer
├── noorle.yaml # Plugin config (optional)
└── dist/ # Build output
├── plugin.wasm # Compiled component
└── *.npack # Deployment archive
File Descriptions
File | Purpose |
---|---|
src/lib.rs | Plugin implementation with exported functions |
wit/world.wit | WebAssembly interface definition |
Cargo.toml | Dependencies and package configuration |
build.sh | Compilation and component creation |
prepare.sh | Installs Rust toolchain and wasm tools |
Implementation Guide
Basic Plugin Structure
rust
// Generate bindings from WIT
wit_bindgen::generate!({
world: "my-plugin-component",
path: "./wit",
});
// Plugin struct
struct MyPluginComponent;
// Implement the Guest trait
impl Guest for MyPluginComponent {
fn say_hi(name: String) -> String {
format!("Hello, {}!", name)
}
fn add(a: f64, b: f64) -> f64 {
a + b
}
}
// Export the component
export!(MyPluginComponent);
Working with WIT
Define your API in wit/world.wit
:
wit
package example:my-plugin;
world my-plugin-component {
/// Greet a person by name
export say-hi: func(name: string) -> string;
/// Add two numbers
export add: func(a: f64, b: f64) -> f64;
/// Process with error handling
export process: func(input: string) -> result<string, string>;
}
Rust-specific binding:
rust
wit_bindgen::generate!({
world: "my-plugin-component",
path: "./wit",
});
impl Guest for MyPlugin {
fn process(input: String) -> Result<String, String> {
// Result maps directly to WIT result type
if input.is_empty() {
Err("Input cannot be empty".to_string())
} else {
Ok(format!("Processed: {}", input))
}
}
}
Error Handling
rust
use anyhow::{Context, Result, bail};
// Internal function with rich error types
fn process_internal(input: &str) -> Result<String> {
if input.is_empty() {
bail!("Input cannot be empty");
}
let data = parse_data(input)
.context("Failed to parse input")?;
Ok(format!("Processed: {:?}", data))
}
// Guest implementation converts to string errors
impl Guest for MyPlugin {
fn process(input: String) -> Result<String, String> {
process_internal(&input)
.map_err(|e| e.to_string())
}
}
Environment Variables
rust
use std::env;
fn get_config() -> Result<String, String> {
// Read environment variable
let api_key = env::var("API_KEY")
.map_err(|_| "API_KEY not set".to_string())?;
// Use with default
let debug = env::var("DEBUG")
.unwrap_or_else(|_| "false".to_string());
Ok(format!("Key: {}, Debug: {}", api_key, debug))
}
JSON Processing
rust
use serde::{Deserialize, Serialize};
use serde_json;
#[derive(Debug, Serialize, Deserialize)]
struct RequestData {
command: String,
value: f64,
metadata: Option<HashMap<String, String>>,
}
impl Guest for MyPlugin {
fn process_json(input: String) -> Result<String, String> {
// Parse JSON
let data: RequestData = serde_json::from_str(&input)
.map_err(|e| format!("Invalid JSON: {}", e))?;
// Process data
let response = RequestData {
command: data.command.to_uppercase(),
value: data.value * 2.0,
metadata: data.metadata,
};
// Serialize back to JSON
serde_json::to_string(&response)
.map_err(|e| format!("Serialization failed: {}", e))
}
}
HTTP Requests (Optional)
Required WIT imports:
wit
world my-plugin-component {
import wasi:http/types@0.2.0;
import wasi:http/outgoing-handler@0.2.0;
export fetch-data: func(url: string) -> result<string, string>;
}
Implementation using waki
:
rust
use waki::Client;
use std::time::Duration;
use anyhow::{bail, Context, Result};
fn fetch_internal(url: &str) -> Result<String> {
let response = Client::new()
.get(url)
.connect_timeout(Duration::from_secs(10))
.send()
.context("Request failed")?;
if response.status_code() != 200 {
bail!("HTTP error: {}", response.status_code());
}
String::from_utf8(response.body()?)
.context("Invalid UTF-8 in response")
}
impl Guest for MyPlugin {
fn fetch_data(url: String) -> Result<String, String> {
fetch_internal(&url).map_err(|e| e.to_string())
}
}
Required permissions:
yaml
permissions:
network:
allow:
- host: "api.example.com"
Testing
Local Testing with Wasmtime
bash
# Build the plugin
noorle plugin build
# Test with WAVE format
wasmtime run --invoke 'say_hi("World")' dist/plugin.wasm
wasmtime run --invoke 'add(2.5, 3.7)' dist/plugin.wasm
wasmtime run --invoke 'process("test data")' dist/plugin.wasm
# With environment variables
wasmtime run --env API_KEY=test123 --invoke 'process("test")' dist/plugin.wasm
Language-Specific Features
Cargo Component
Rust uses cargo-component
for building WASM components:
toml
# Cargo.toml
[package.metadata.component]
package = "example:my-plugin"
[package.metadata.component.dependencies]
Type-Safe Bindings
rust
// WIT types become Rust types
// WIT: record person { name: string, age: u32 }
// Rust:
#[derive(Debug)]
struct Person {
name: String,
age: u32,
}
// Enums map directly
// WIT: enum status { pending, processing, complete }
// Rust:
#[derive(Debug)]
enum Status {
Pending,
Processing,
Complete,
}
Build Configuration
Dependencies
Cargo.toml:
toml
[dependencies]
wit-bindgen = "0.46.0" # Required for WIT bindings
anyhow = "1.0" # Error handling
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" # JSON support
waki = "0.5.1" # HTTP client (optional)
chrono = "0.4" # Date/time (optional)
[lib]
crate-type = ["cdylib"] # Required for WASM
[profile.release]
opt-level = "z" # Optimize for size
lto = true # Link-time optimization
Build Optimization
toml
# Cargo.toml optimization settings
[profile.release]
opt-level = "z" # Optimize for size
lto = true # Link-time optimization
Build Process
bash
# Build your plugin (handles compilation, optimization, packaging)
noorle plugin build
The build process:
- Compiles to wasm32-wasip2 target
wkg
fetches WIT dependencies from imports- Creates component with cargo-component
- Optimizes with wasm-tools
- Packages into .npack archive
Troubleshooting
Common Issues
Build Fails
Problem: error: target 'wasm32-wasip2' not found
Solution:
bash
rustup target add wasm32-wasip2
Runtime Error
Problem: Guest export 'process' not found
Solution:
rust
// Ensure export macro is present
export!(MyPluginComponent);
// Check struct name matches
struct MyPluginComponent; // Must match export
Cargo Component Issues
Problem: cargo-component command not found
Solution:
bash
cargo install cargo-component
Debugging
Enable debug output:
bash
DEBUG=1 noorle plugin build
Check generated bindings:
bash
ls -la target/wasm32-wasip2/release/
View WASM exports:
bash
wasm-tools component wit dist/plugin.wasm
Performance
Optimization tips:
- Use
--release
for production builds - Enable LTO in Cargo.toml
- Minimize dependencies
- Use
wasm-opt
for additional optimization - Profile with
wasmtime --profile
Best Practices
Do's
- ✅ Use
anyhow
for internal error handling - ✅ Convert to string errors at boundaries
- ✅ Validate input early and return clear errors
- ✅ Use
serde
for JSON parsing - ✅ Keep dependencies minimal for smaller binaries
- ✅ Use
cargo clippy
for linting
Don'ts
- ❌ Don't use
panic!
orunwrap()
in production - ❌ Don't block on async operations (no tokio)
- ❌ Don't use heavy dependencies unnecessarily
- ❌ Don't leak sensitive data in errors
Security
- Validate all inputs with strong types
- Use minimal permissions in noorle.yaml
- Never log sensitive data
- Sanitize error messages before returning
- Use
#[derive(Debug)]
carefully with sensitive structs
Advanced Topics
Custom WASI Interfaces
wit
// Advanced WIT with custom types
interface types {
record request {
id: string,
timestamp: u64,
payload: list<u8>,
}
record response {
status: u16,
body: string,
}
}
world my-plugin {
use types.{request, response};
export process-request: func(req: request) -> response;
}
rust
// Implementation with custom types
impl Guest for MyPlugin {
fn process_request(req: Request) -> Response {
Response {
status: 200,
body: format!("Processed request {}", req.id),
}
}
}
Performance Monitoring
rust
use std::time::Instant;
fn timed_operation<F, R>(name: &str, f: F) -> R
where
F: FnOnce() -> R
{
let start = Instant::now();
let result = f();
let duration = start.elapsed();
eprintln!("{} took {:?}", name, duration);
result
}
impl Guest for MyPlugin {
fn process(input: String) -> Result<String, String> {
timed_operation("processing", || {
process_internal(&input)
}).map_err(|e| e.to_string())
}
}
Working with Custom Types
rust
// Complex types from WIT
use crate::bindings::types::{Request, Response};
impl Guest for MyPlugin {
fn handle_complex(req: Request) -> Result<Response, String> {
// Process complex types
Ok(Response {
status: 200,
headers: req.headers,
body: process_body(req.body)?,
})
}
}
Resources
Documentation
Tools
- cargo-component - Build WASM components
- wasm-tools - WASM utilities
- wasmtime - WASM runtime for testing
Examples
Next Steps
Continue Learning
- Other Language Guides - Explore Python, TypeScript, JavaScript, Go, Rust
- Configuration - Advanced plugin settings
- Publishing - Deploy your plugin
Build Your Plugin
- Quick Start - Step-by-step guide
- Project Structure - Understanding plugin anatomy
- Platform Overview - How plugins work in Noorle