Rhai Cheatsheet¶
Rhai syntax and idioms for Kelora scripts. Which variables (e, meta, conf, state, window, metrics, span) exist in which stage: Script Variables. All functions: Functions or kelora --help-functions [KEYWORD].
Values¶
let n = 42; // i64
let f = 19.99; // f64
let s = "alice"; // string: double quotes ('a' is a single char)
let re = #"\d+\.\d+"#; // raw string: no escapes, use for regexes
let q = ##"has "quotes""##; // more # to allow " inside
let msg = `user ${s} has ${n + 1}`; // interpolation: backticks only, \${ for a literal ${
let ok = true;
let tags = [1, "two", 3.0]; // array, mixed types ok
let user = #{name: "bob", age: 30}; // map
let none = (); // unit: Rhai's "nothing" (no null/undefined)
type_of(n) // "i64", "f64", "string", "bool", "array", "map", "()"
letis required to create a variable; plainx = 1fails withVariable not found.- Variables are dynamically typed:
let x = 1; x = "s";is fine. - Arrays and maps are copied on assignment:
let b = a; b.push(2);leavesaunchanged. - Comments:
// line,/* block */.
Operators¶
Arithmetic + - * / % ** (power)
Comparison == != < > <= >=
Logical && || !
Bitwise & | ^ << >>
Assignment = += -= *= /= %= &= |= ^= <<= >>=
Default a ?? b a unless it is (); b is evaluated only if needed
Membership "key" in map, x in array
Ranges 1..5 (1–4), 1..=5 (1–5), in for loops and switch arms
+ with a string on either side concatenates: "5" + 3 is "53", "n=" + () is "n=". To add numbers, convert first: "5".to_int() + 3.
Assignment is a statement, not an expression: let n = (x = 1); does not compile.
Control flow¶
if x > 10 { "big" } else if x > 5 { "medium" } else { "small" } // braces required
let size = if x > 10 { "big" } else { "small" }; // if is an expression
let kind = switch e.status {
200 | 204 => "ok",
400..=499 => "client error",
_ => "other"
};
for i in 0..10 { ... }
for item in e.items { ... }
for key in e.keys() { print(`${key}=${e[key]}`); } // maps aren't iterable: use keys()/values()
while cond { if done { break; } if skip { continue; } }
loop { if stop { break; } }
Semicolons separate statements; they are optional only after a block } and after the last statement.
Functions, closures, method calls¶
fn add(a, b) { a + b } // last expression is the return value
fn greet(name) { return "hi " + name; }
let double = |x| x * 2;
[1, 2, 3].map(|x| x * 2) // [2, 4, 6]
[1, 2, 3].filter(|x| x > 1) // [2, 3]
[1, 2, 3].reduce(|acc, x| acc + x, 0)
Any built-in can be called as a method on its first argument, which allows chaining:
Your own functions (inline fn or --include) must be called function-style: is_problem(e), not e.is_problem() (Function not found). A fn defined in one --exec is not visible in the next; use --include to share it.
Strings¶
e.msg.to_lower() e.msg.to_upper() // new string
e.msg.strip() // new string, whitespace trimmed
e.msg.contains("timeout")
e.msg.starts_with("GET") e.msg.ends_with(".json")
e.msg.split(" ") // array
e.msg.replace_regex(#"\d+"#, "N") // new string
e.msg.extract_regex(#"user=(\w+)"#, 1) // "" if no match
e.msg.extract_regex(#"user=(\w+)"#, 1).or_empty() // () if no match, so the field is not set
trim(), replace(), sort(), reverse() modify in place and return ()
These Rhai built-ins change the variable they are called on and return nothing.
e.y = e.msg.trim() leaves y unset, and e.msg.trim().to_upper() fails.
Use the value-returning versions: strip(), replace_regex(), sorted(), reversed().
Event fields¶
e.level // top-level field
e.user.name // nested map
e.scores[0] e.scores[-1] // array index, negative counts from the end
e["user-agent"] e[name] // non-identifier or dynamic key (e.user-agent is subtraction)
e.status = 500; // set
e.password = (); // remove field
e = (); // drop the event
m.key and m["key"] are the same for identifier keys. On a map, bare e.len reads a field called len; use e.len() or len(e) for the field count.
Missing fields¶
A missing field reads as (). What happens next depends on the operation:
| Expression | Result |
|---|---|
e.missing == "x", e.missing > 5 |
false |
"took " + e.missing |
"took " |
e.missing + 1 |
error |
e.missing.to_upper() |
error |
e.user.role with user absent |
error |
Safe forms:
e.dur ?? 0 // default for a top-level field
e.get("dur", 0) // same, method style
e.get_path("user.role", "guest") // dotted path, also "items[0].id"
e.has("dur") // true if present and not ()
"dur" in e // true if present
e.has_path("user.role")
Arrays and maps¶
e.tags.len() e.tags.is_empty() e.tags.contains("x")
e.tags.join(", ")
sorted(e.scores) // new array, numeric or lexicographic
reversed(e.items)
unique(e.tags) // keeps first occurrence
sorted_by(e.users, "age") // array of maps, by field
e.users.pluck("name") // field from each map, skips missing
e.scores.slice("-3:") // Python-style slice spec
e.scores.sum() e.scores.min() e.scores.max() e.scores.mean()
// highest scorer first, names only
e.ranking = sorted_by(e.users, "score").reversed().pluck("name");
emit_each(e.items) // one event per element; the original is dropped
emit_each(e.items, #{batch: e.id}) // add fields to each
e.keys() e.values() e.contains("k")
Nested fan-out takes one --exec per level:
kelora -j batches.json \
-e 'emit_each(e.batches)' \
-e 'emit_each(e.items, #{batch_id: e.id})' \
--filter 'e.status == "active"'
Conversions¶
to_int("42") to_float("1.5") to_bool("true") // () if conversion fails
e.port.to_int_or(8080) // default instead of ()
e.price.to_float_or(0.0)
e.active.to_bool_or(false)
to_string(42) 42.to_string()
Errors¶
try {
e.n = e.raw.to_int() + 1;
} catch (err) {
eprint(err); // err is a map with message, line, position
}
Guards (??, to_int_or, has) are cheaper than try. Without --strict, a failing --filter counts as false and a failing --exec is rolled back (the event continues unchanged from before that stage); both are reported as warnings and the exit code stays 0. With --strict the first error aborts with exit code 1. Details: When something goes wrong.
Coming from other languages¶
| Habit | In Rhai |
|---|---|
null, None, undefined |
() |
'text' |
"text" ('t' is a single character) |
r"\d+" |
#"\d+"# |
if x > 5: |
if x > 5 { ... } |
x = 1 to declare |
let x = 1; |
"5" + 3 == 8 |
"53"; convert with to_int() |
arr[-3:] |
arr.slice("-3:") |
for k in dict |
for k in map.keys() |
s.trim() returns a string |
s.strip() |
More: Scripting guide, kelora --help-rhai, rhai.rs.