Skip to main content

Rhai Scripting Guide

Rhai is a lightweight, high-performance scripting language designed for embedding in Rust applications. MoFA uses Rhai for runtime plugins, workflow orchestration, and dynamic business rules.

Language Basics

Variables and Types

// Variables
let name = "Alice";
let age = 30;
let height = 5.8;
let is_active = true;

// Constants
const MAX_RETRIES = 3;
const API_VERSION = "v1";

// Type inference
let auto_typed = 42;           // Integer
let also_auto = "text";        // String
let mixed = [1, "two", 3.0];  // Array with mixed types

Data Types

// Integers
let int = 42;
let negative = -10;
let hex = 0xFF;      // 255
let binary = 0b1010; // 10

// Floats
let pi = 3.14159;
let scientific = 1.5e-3;

// Operations
let sum = 10 + 5;       // 15
let diff = 10 - 5;      // 5
let product = 10 * 5;   // 50
let quotient = 10 / 5;  // 2
let remainder = 10 % 3; // 1
let power = 2 ** 3;     // 8

Control Flow

Conditionals

// If-else
if age >= 18 {
    print("Adult");
} else {
    print("Minor");
}

// If-else if-else
if score >= 90 {
    print("A");
} else if score >= 80 {
    print("B");
} else if score >= 70 {
    print("C");
} else {
    print("F");
}

// Ternary operator
let status = is_active ? "active" : "inactive";

// Switch (pattern matching)
switch value {
    0 => print("Zero"),
    1 => print("One"),
    2 | 3 => print("Two or Three"),
    _ => print("Other")
}

Loops

// Iterate over array
let items = ["apple", "banana", "cherry"];
for item in items {
    print(item);
}

// Iterate over range
for i in 0..10 {
    print(i);  // 0 to 9
}

// Inclusive range
for i in 0..=10 {
    print(i);  // 0 to 10
}

// Iterate with index
for (i, item) in items.enumerate() {
    print(`${i}: ${item}`);
}

Functions

Function Definition

examples/rhai_scripting/src/main.rs
// Simple function
fn greet(name) {
    "Hello, " + name + "!"
}

// Function with multiple parameters
fn add(a, b) {
    a + b
}

// Function with default behavior
fn multiply(a, b) {
    if b == () {
        b = 1;  // Default value
    }
    a * b
}

// Recursive function
fn factorial(n) {
    if n <= 1 {
        1
    } else {
        n * factorial(n - 1)
    }
}

// Function returning object
fn create_user(name, age) {
    #{
        name: name,
        age: age,
        created_at: now()
    }
}

Return Values

// Explicit return
fn get_status(code) {
    if code == 200 {
        return "OK";
    } else if code == 404 {
        return "Not Found";
    }
    return "Unknown";
}

// Implicit return (last expression)
fn double(x) {
    x * 2  // Automatically returned
}

// Multiple returns
fn divide(a, b) {
    if b == 0 {
        return ();
    }
    a / b
}

Error Handling

// Throw errors
fn validate_age(age) {
    if age < 0 {
        throw "Age cannot be negative";
    }
    if age > 150 {
        throw "Age is unrealistic";
    }
    age
}

// Try-catch pattern
fn safe_divide(a, b) {
    if b == 0 {
        return ();
    }
    a / b
}

let result = safe_divide(10, 0);
if result == () {
    print("Division by zero");
} else {
    print(`Result: ${result}`);
}

MoFA Built-in Functions

Logging

// Basic logging
log("This is an info message");
debug("Debug information");
warn("Warning message");
error("Error occurred");

// Formatted logging
let user_id = 123;
log(`User ${user_id} logged in`);

JSON Operations

examples/rhai_scripting/src/main.rs
// Parse JSON string
let json_str = '{"name":"Alice","age":30}';
let obj = parse_json(json_str);
print(obj.name);  // "Alice"

// Convert to JSON
let data = #{
    name: "Bob",
    age: 25,
    tags: ["user", "active"]
};
let json = to_json(data);
print(json);  // {"name":"Bob","age":25,"tags":["user","active"]}

String Functions

examples/rhai_scripting/src/main.rs
let text = "  Hello World  ";

// Trimming
let trimmed = trim(text);        // "Hello World"
let trim_start = trim_start(text); // "Hello World  "
let trim_end = trim_end(text);   // "  Hello World"

// Case conversion
let upper = upper(text);         // "  HELLO WORLD  "
let lower = lower(text);         // "  hello world  "

// String checking
let starts = starts_with(text, "  H");  // true
let ends = ends_with(text, "d  ");      // true
let contains = contains(text, "World"); // true

// Replacement
let replaced = replace(text, "World", "Rust");

Time Functions

// Current timestamp (milliseconds since epoch)
let timestamp = now();
print(`Current time: ${timestamp}`);

// Use in data
let event = #{
    id: "evt_001",
    created_at: now(),
    data: "some data"
};

Type Conversion

// To string
let str = to_string(42);      // "42"
let str2 = to_string(3.14);   // "3.14"

// To integer
let int = to_int("42");       // 42
let int2 = to_int(3.14);      // 3

// To float
let float = to_float("3.14"); // 3.14
let float2 = to_float(42);    // 42.0

Workflow Scripting

Task Nodes

examples/rhai_scripting/src/main.rs
// Simple task
fn validate_input(input) {
    log("Validating input...");
    if input.value < 0 {
        throw "Value cannot be negative";
    }
    input
}

// Transform task
fn transform_data(input) {
    log("Transforming data...");
    #{
        original: input.value,
        doubled: input.value * 2,
        squared: input.value * input.value,
        timestamp: now()
    }
}

// Format task
fn format_output(input) {
    log("Formatting output...");
    `Result: original=${input.original}, doubled=${input.doubled}, squared=${input.squared}`
}

Conditional Nodes

examples/rhai_scripting/src/main.rs
// Routing based on score
fn check_score(input) {
    let score = input.score;

    if score >= 90 {
        #{score: score, rating: "excellent"}
    } else if score >= 70 {
        #{score: score, rating: "good"}
    } else if score >= 60 {
        #{score: score, rating: "pass"}
    } else {
        #{score: score, rating: "fail"}
    }
}

Rule Engine Scripting

Rule Conditions

examples/rhai_scripting/src/main.rs
// Simple condition
user.is_vip == true

// Complex condition
user.is_vip == true && order.total >= 1000

// Multi-line condition
let content = lower(text);
contains(content, "spam") ||
contains(content, "click here") ||
contains(content, "free money")

// Function-based condition
fn is_eligible(user, order) {
    user.age >= 18 &&
    user.verified == true &&
    order.total >= 100
}

is_eligible(user, order)

Rule Actions

examples/rhai_scripting/src/main.rs
// Discount calculation
let discount = 0.8;
let final_price = order.total * discount;
#{
    rule: "vip_discount",
    discount_rate: discount,
    original_price: order.total,
    final_price: final_price,
    saved: order.total - final_price
}

// Content moderation
#{
    status: "rejected",
    reason: "Suspected spam",
    confidence: 0.95
}

// Data enrichment
#{
    user: user,
    enriched_data: #{
        segment: calculate_segment(user),
        lifetime_value: calculate_ltv(user),
        risk_score: calculate_risk(user)
    }
}

Tool Scripting

Dynamic Tools

examples/rhai_scripting/src/main.rs
// Calculator tool
let a = params.a;
let b = params.b;
let op = params.operation;

let result = if op == "add" { a + b }
else if op == "sub" { a - b }
else if op == "mul" { a * b }
else if op == "div" {
    if b == 0.0 { throw "Division by zero"; }
    a / b
}
else if op == "pow" {
    let r = 1.0;
    for i in 0..b.to_int() { r *= a; }
    r
}
else { throw "Unknown operation: " + op; };

#{
    operation: op,
    a: a,
    b: b,
    result: result,
    expression: `${a} ${op} ${b} = ${result}`
}

String Processing Tool

examples/rhai_scripting/src/main.rs
let text = params.text;
let ops = params.operations;

for op in ops {
    if op == "trim" {
        text = trim(text);
    } else if op == "upper" {
        text = upper(text);
    } else if op == "lower" {
        text = lower(text);
    } else if op == "reverse" {
        let chars = text.chars();
        let reversed = "";
        for i in range(0, chars.len()) {
            reversed = chars[chars.len() - 1 - i] + reversed;
        }
        text = reversed;
    }
}

#{
    original: params.text,
    processed: text,
    operations: ops
}

Advanced Patterns

Caching

// Global cache
let cache = #{};
let cache_hits = 0;
let cache_misses = 0;

fn get_with_cache(key) {
    if cache.contains(key) {
        cache_hits += 1;
        return cache[key];
    }

    cache_misses += 1;
    let value = expensive_computation(key);
    cache[key] = value;
    value
}

fn get_cache_stats() {
    #{
        hits: cache_hits,
        misses: cache_misses,
        hit_rate: cache_hits / (cache_hits + cache_misses)
    }
}

State Machines

let state = "idle";

fn transition(event) {
    if state == "idle" && event == "start" {
        state = "running";
        log("Started");
    } else if state == "running" && event == "pause" {
        state = "paused";
        log("Paused");
    } else if state == "paused" && event == "resume" {
        state = "running";
        log("Resumed");
    } else if state == "running" && event == "stop" {
        state = "idle";
        log("Stopped");
    } else {
        throw `Invalid transition: ${state} -> ${event}`;
    }
    state
}

Data Pipelines

// Pipeline functions
fn filter_active(items) {
    let result = [];
    for item in items {
        if item.active {
            result.push(item);
        }
    }
    result
}

fn enrich_items(items) {
    let result = [];
    for item in items {
        item.enriched_at = now();
        item.score = calculate_score(item);
        result.push(item);
    }
    result
}

fn sort_by_score(items) {
    // Simple bubble sort
    let arr = items;
    let len = arr.len();
    for i in 0..len {
        for j in 0..(len - i - 1) {
            if arr[j].score < arr[j + 1].score {
                let temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
    arr
}

// Execute pipeline
fn execute(input) {
    let data = parse_json(input);
    let filtered = filter_active(data.items);
    let enriched = enrich_items(filtered);
    let sorted = sort_by_score(enriched);

    to_json(#{
        items: sorted,
        count: sorted.len(),
        processed_at: now()
    })
}

Best Practices

// Good
fn calculate_user_discount(user, order) { ... }

// Bad
fn calc(u, o) { ... }
fn process_order(order) {
    // Validate first
    if order.items.len() == 0 {
        throw "Order must have items";
    }
    if order.total <= 0 {
        throw "Invalid order total";
    }

    // Then process
    // ...
}
// Good
const MAX_RETRIES = 3;
const TIMEOUT_MS = 5000;

if retry_count >= MAX_RETRIES {
    throw "Max retries exceeded";
}

// Bad
if retry_count >= 3 { ... }
// Calculate weighted score based on multiple factors
// Formula: (quality * 0.5) + (speed * 0.3) + (cost * 0.2)
fn calculate_score(quality, speed, cost) {
    (quality * 0.5) + (speed * 0.3) + (cost * 0.2)
}
fn process_data(data) {
    // Handle errors first
    if data == () {
        return ();
    }
    if data.len() == 0 {
        return [];
    }

    // Main logic
    // ...
}

Performance Tips

Cache Expensive Operations

Store results of expensive computations in global variables

Avoid Deep Nesting

Keep function nesting shallow for better performance

Use Early Returns

Exit functions early to avoid unnecessary computation

Minimize Array Copies

Modify arrays in place when possible instead of creating new ones

Next Steps

Runtime Plugins

Build complete Rhai plugins

Workflow Guide

Create dynamic workflows with Rhai

Build docs developers (and LLMs) love