A small, statically-typed functional scripting language for Rust hosts. Whole-module type inference, Rust-style syntax, no ambient I/O.
Created by Enlightware GmbH for use in Candli, an educational game engine that teaches children STEAM through visual programming. Ferlium powers Candli's advanced script blocks: end users write logic the game engine loads, compiles, and runs.
A small Ferlium program:
fn classify(n) {
if n < 0 {
"negative"
} else if n == 0 {
"zero"
} else {
"positive"
}
}
[-1, 0, 1, 2] |> map(classify)
classify has no type annotations; Ferlium infers (int) -> string, generalising or specialising as needed.
The |> operator pipes the array into map.
map lives in the pre-imported std module.
Embedding it from a Rust host (see minimal example):
use ferlium::{CompilerSession, Path, run_fn_native};
let mut session = CompilerSession::new();
let module_id = session
.compile(
"fn answer() -> int { 42 }",
"demo.fer",
Path::single_str("demo"),
)
.unwrap()
.module_id;
let result: isize = run_fn_native!(&session, module_id, "answer", [] -> isize).unwrap();
assert_eq!(result, 42);The same CompilerSession API is what powers the Playground, the REPL, the benchmark suite (benches/benches.rs), and the WebAssembly playground (playground/).
- Type inference with generics. Ferlium infers generic function signatures across a whole module, so
fn add(a, b) { a + b }needs no annotations. Its inference is Hindley–Milner-style with constrained types; partial annotations can use_. - Mutable value semantics. Values can be safely mutated with the help of a small borrow checker, without a garbage collector or explicit lifetime annotations.
- Algebraic data types. Records, tuples and tagged unions can be open to additional fields or variants through row polymorphism, or closed. Independently, each can remain structural or gain nominal identity through a Rust-style newtype.
- Traits and modules. Traits (type classes) support multiple parameters and associated types, while
module::nameanduse module::*;organize code. - Functional core. First-class and anonymous functions with closures, pattern matching for destructuring data, and
|>pipelining for chained transformations. - Subscripts and custom projections. Array indexing and custom projections use subscripts to read or update places; scoped subscripts can run code around an access with
yield. - Compile-time effect tracking. Ferlium infers
read,write, andfallibleeffects and shows them in signatures and diagnostics, so you can see which functions touch host state or may fail before running them. - Optimizing compiler with WebAssembly output. Ferlium optimizes its intermediate representation before generating WebAssembly for browser and Node hosts.
- Online playground: https://enlightware.github.io/ferlium/playground/
- REPL:
cargo run --example ferlium(useprint(string)in REPL to print to console). Pass--allow-experimentalto the example REPL or pipe mode to try safe, experimental language features. - Book: https://enlightware.github.io/ferlium/book/
- Chat: ferlium.zulipchat.com
Ferlium occupies a niche similar to Lua, Rhai, Rune, or Boa as a script engine for Rust applications, but it is the only one in that group with full Hindley–Milner type inference and a compile-time effect system. Other engines in this space are dynamically typed (Lua, Rhai, Rune, Boa); some offer optional gradual typing. If your host application benefits from catching type errors before the script runs — and from being able to read at a glance whether a function is pure, reads host state, writes host state, or can fail — Ferlium's tradeoffs are aimed at that case.
The integration story also differs from other Rust-embedded scripting engines:
- No native-type duplication. Ferlium's intermediate representation does not re-declare primitives like
intandboolas language constructs — they remain native Rust types throughout, which keeps host bindings lean. - Direct binding to Rust functions. Native Rust functions are exposed as Ferlium-callable functions through direct binding in the interpreters and a shared ABI for generated WebAssembly — no value-conversion ritual, no generated glue. The host chooses which functions scripts can call; scripts have no ambient I/O.
- Native value handles for host objects. Host-owned objects can be wrapped as opaque Ferlium values and threaded through scripts — useful for game-engine entities, GPU resources, file descriptors, and the like — without copying.
Ferlium is pre-1.0; expect breaking changes in syntax, APIs, and standard library.
- The compiler is single-threaded — it currently panics if its interned type-universe lock is contended.
- No file-based module discovery from script source: module organisation is the host's responsibility (see the Modules chapter of the book).
The issue tracker lists planned features.
Design intent: bring the expressive power of ML-family type systems to people who reach for Python or JavaScript — without asking them to learn category theory first. Its inspirations are:
- ML: basic functional concepts (especially this course and this one)
- Rust: syntax
- Haskell: type system
- HM(X) approach to type inference (paper)
- Mutable Value Semantics (especially this paper)
- This series of blog posts
Run make repl-hir or make repl-mir for the native REPL, or make repl-wasm
for the Node/Wasm flavour. See the REPL guide for shared commands and
scripted Wasm sessions.
make test-local runs the suite via nextest, which is significantly faster than cargo test.
Run make bench for instruction counts, make profile-mir for a quick optimizer comparison, or make bench-wasm for WebAssembly timings.
Setup, Callgrind measurements, options, and caveats are in the benchmark guide.
Crash-oriented fuzz targets live in fuzz/ and are run explicitly with cargo-fuzz; see doc/fuzzing.md.
See CONTRIBUTING.md for how to contribute.
Ferlium is copyright Enlightware GmbH and licensed under the Apache 2.0 license.
"Ferlium" is a trademark of Enlightware GmbH; see TRADEMARK.md for permitted uses.
The tests in tests/language/ constitute the official Ferlium Conformance Suite.