Skip to content

Rhai Cheatsheet

Quick reference for Rhai scripting in Kelora. For detailed tutorials, see Scripting Transforms. For practical examples: kelora --help-examples. For function reference: kelora --help-functions.

Variables & Types

let x = 42;                          // Integer (i64)
let price = 19.99;                   // Float (f64)
let name = "alice";                  // String (double quotes only!)
let active = true;                   // Boolean (true/false)
let tags = [1, "two", 3.0];          // Array (mixed types ok)
let user = #{name: "bob", age: 30};  // Map/object literal
let empty = ();                      // Unit type (Rhai's "nothing")

type_of(x)                           // Returns: "i64", "string", "array", "map", "()"
x = "hello";                         // Dynamic typing: can change type

Key Points:

  • let required for new variables (no implicit declaration)
  • Double quotes only for strings ("text" not 'text')
  • Unit type () represents "nothing" (not null/undefined)
  • Arrays and maps are reference types (modifying copies affects original)

Operators

// Arithmetic
a + b    a - b    a * b    a / b    a % b    a ** b  // power: 2**3 == 8

// Comparison
a == b   a != b   a < b    a > b    a <= b   a >= b

// Logical
a && b   a || b   !a

// Bitwise
a & b    a | b    a ^ b    a << b   a >> b

// Assignment
a = b    a += b   a -= b   a *= b   a /= b   a %= b
a &= b   a |= b   a ^= b   a <<= b  a >>= b

// Ranges (for loops only)
1..5     // Exclusive: 1, 2, 3, 4
1..=5    // Inclusive: 1, 2, 3, 4, 5

// Membership
"key" in map                         // Check if key exists

Control Flow

If-Else

if x > 10 {
    print("big");
} else if x > 5 {
    print("medium");
} else {
    print("small");
}

// Ternary-style (if is an expression)
let category = if x > 10 { "big" } else { "small" };

Switch

let category = switch x {
    1 => "one",
    2 | 3 => "two or three",          // Multiple cases
    4..=6 => "four to six",            // Range matching
    _ => "other"                       // Default (underscore)
};

Loops

// Range loops
for i in 0..10 { print(i); }          // 0 to 9
for i in 0..=10 { print(i); }         // 0 to 10

// Array iteration
for item in array { print(item); }

// Map iteration
for (key, value) in map {
    print(`${key} = ${value}`);
}

// While loop
while condition {
    if done { break; }
    if skip { continue; }
}

// Infinite loop
loop {
    if should_stop { break; }
}

Functions & Closures

// Function definition
fn add(a, b) {
    a + b                             // Last expr is return value
}

fn greet(name) {
    return "Hello, " + name;          // Explicit return
}

// Closures
let double = |x| x * 2;
let add = |a, b| a + b;

// Closures in array methods
[1, 2, 3].map(|x| x * 2)              // [2, 4, 6]
[1, 2, 3].filter(|x| x > 1)           // [2, 3]

Rhai Special Feature: Function-as-Method

Rhai allows calling any function as a method on its first argument:

// These are equivalent:
extract_re(e.line, r"\d+")            // Function call style
e.line.extract_re(r"\d+")             // Method call style

// Use method style for chaining:
e.domain = e.url
    .extract_domain()
    .to_lower()
    .strip();

// Both styles work for all functions:
to_int(e.port)                        // Function style
e.port.to_int()                       // Method style (more readable)

Kelora Event Access

The global variable e represents the current event in --filter and --exec stages:

// Direct field access
e.level                               // Top-level field
e.user.name                           // Nested field (maps)
e.scores[1]                           // Array indexing (0-based)
e.scores[-1]                          // Negative indexing (last element)
e.headers["user-agent"]               // Bracket notation for special chars

// Field existence checking
"field" in e                          // Check top-level field exists
e.has_path("user.role")               // Check nested path exists

// Safe field access with defaults
e.get_path("user.role", "guest")      // Get nested with fallback
e.get_path("scores[0]", 0)            // Works with array paths

// Field removal
e.password = ()                       // Remove field (unit assignment)
e.ssn = ()                            // Remove another field
e = ()                                // Remove entire event (filtered out)

Array & Map Operations

JSON arrays become native Rhai arrays with full functionality:

// Array transformations
sorted(e.scores)                      // Sort numerically/lexicographically
reversed(e.items)                     // Reverse order
unique(e.tags)                        // Remove duplicates
dedup(e.values)                       // Remove consecutive duplicates
sorted_by(e.users, "age")             // Sort objects by field

// Array methods
e.tags.len()                          // Length
e.tags.is_empty()                     // Check if empty
e.tags.join(", ")                     // Join to string
e.scores.sum()                        // Sum numbers
e.scores.min()                        // Minimum value
e.scores.max()                        // Maximum value

// Array access patterns
if e.items.len() > 0 {
    e.first = e.items[0];
    e.last = e.items[-1];
}

// Fan-out: convert array elements to separate events
emit_each(e.items)                    // Each element becomes an event
emit_each(e.items, #{ctx: "value"})   // Add base fields to each

// Map operations
for (key, val) in e {
    print(`${key} = ${val}`);
}

Type Conversions

// Strict conversions (return () on error)
to_int(e.port)                        // String → integer
to_float(e.price)                     // String → float
to_bool(e.active)                     // String → boolean

// Safe conversions with defaults
to_int_or(e.port, 8080)               // Use default if conversion fails
to_float_or(e.price, 0.0)
to_bool_or(e.active, false)

// String conversions
to_string(42)                         // Any → string
e.value.to_int()                      // Method style

// Type checking
type_of(e.field)                      // Get type as string
type_of(e.field) != "()"              // Check if field has value

Common Patterns

Safe Nested Access

// With default fallback
let role = e.get_path("user.role", "guest");
let port = to_int_or(e.port, 8080);

// With existence check
if e.has_path("user.profile.avatar") {
    e.avatar = e.user.profile.avatar;
}

// Safe array access
if e.items.len() > 0 {
    e.first_item = e.items[0];
}

Conditional Field Removal

// Remove debug fields in production
if e.level != "DEBUG" {
    e.stack_trace = ();
    e.debug_info = ();
}

// Remove entire event conditionally
if e.status < 400 { e = (); }         // Only keep errors

Method Chaining

// Extract and normalize domain
e.domain = e.url
    .extract_domain()
    .to_lower()
    .strip();

// Parse and extract from structured text
e.error_line = e.stack_trace
    .extract_re(r"line (\d+)", 1)
    .to_int_or(0);

Array Processing

// Get top N scores
e.top_3 = sorted(e.scores)[-3:];

// Extract names from sorted users
e.winners = sorted_by(e.users, "score")
    .reverse()
    .map(|u| u.name);

// Filter and count
e.active_items = e.items.filter(|i| i.status == "active");
e.active_count = e.active_items.len();

Multi-Level Fan-Out

# First exec: batches → separate events
--exec 'emit_each(e.batches)'

# Second exec: items → separate events with context
--exec 'let ctx = #{batch_id: e.id}; emit_each(e.items, ctx)'

# Filter the final events
--filter 'e.status == "active"'

Global Context

conf                                  // Global config map (read-only after --begin)
metrics                               // Global metrics map (from track_* calls)
meta                                  // Event metadata (filename, line numbers, raw line)
get_env("VAR", "default")             // Environment variable access

// meta attributes:
meta.line                             // Original raw line (always available)
meta.line_num                         // Line number, 1-based (available with files)
meta.filename                         // Source filename (multi-file processing)

// Example usage:
--begin 'conf.env = get_env("ENVIRONMENT", "dev")'
--filter 'conf.env == "prod" || e.level == "ERROR"'

// Multi-file tracking
--exec 'if e.level == "ERROR" { track_count(meta.filename) }'

// Debugging with line numbers
--exec 'eprint("Error at " + meta.filename + ":" + meta.line_num)'

Error Handling Modes

Default (resilient):

  • Parse errors → skip line, continue
  • Filter errors → treat as false, drop event
  • Exec errors → rollback, keep original event

Strict mode (--strict):

  • Any error → abort immediately with exit code 1

Rhai Quirks & Gotchas

Coming from Watch out for
JavaScript No null/undefined (use ()), no single quotes, let required
Python Braces required, no : after if/for, != not <>, double quotes only
Rust More permissive syntax, semicolons mostly optional, dynamic typing

Common mistakes:

// ❌ Wrong
x = 1                                 // Error: x not declared
let name = 'alice';                   // Error: single quotes not allowed
if x > 5: print("big")                // Error: colon not allowed, braces required
"5" + 3                               // Error: no implicit conversion

// ✅ Correct
let x = 1;                            // Declare with let
let name = "alice";                   // Double quotes
if x > 5 { print("big"); }            // Braces required
"5".to_int() + 3                      // Explicit conversion

Special behaviors:

  • Last expression in block is return value (no return needed)
  • Semicolons recommended but often optional
  • Function calls without args: e.len same as e.len()
  • No implicit type conversion (use to_int(), to_float(), etc.)

Quick Reference

// Event manipulation
e.field = value                       // Set field
e.field = ()                          // Remove field
e = ()                                // Remove event

// Type checking
type_of(e.field)                      // Get type
"field" in e                          // Field exists

// Safe access
e.get_path("a.b.c", default)          // Nested with fallback
e.has_path("a.b.c")                   // Check nested exists

// Conversions
to_int_or(val, 0)                     // Safe int conversion
to_float_or(val, 0.0)                 // Safe float conversion
to_bool_or(val, false)                // Safe bool conversion

// Arrays
e.items.len()                         // Length
e.items.is_empty()                    // Check empty
sorted(e.items)                       // Sort
unique(e.items)                       // Deduplicate
emit_each(e.items)                    // Fan out to events

// Strings
e.text.to_lower()                     // Lowercase
e.text.to_upper()                     // Uppercase
e.text.strip()                        // Trim whitespace
e.text.contains("word")               // Substring check
e.text.extract_re(r"(\d+)", 1)        // Regex extraction

// Environment & Context
get_env("VAR", "default")             // Get env var
conf.key                              // Read config (from --begin)
metrics.key                           // Read metrics (in --end)
meta.filename                         // Current source filename
meta.line_num                         // Current line number (1-based)
meta.line                             // Original raw line

See Also

  • Scripting Transforms Tutorial - Detailed walkthrough with examples
  • kelora --help-functions - Complete function catalogue
  • kelora --help-examples - Practical log analysis patterns
  • kelora --help-rhai - Language guide (this cheatsheet's source)
  • Rhai Documentation - Full Rhai language reference