Rhai: an embedded scripting engine for Rust applications
Rhai - An embedded scripting language for Rust.
At a glance
- What is it?
- Rhai adds a sandboxed, dynamically typed scripting language to a Rust program through a single crate. It is a good fit when end users or generated scripts need to drive your application logic, and a poor fit when you want an interpreter that runs arbitrary Python or JavaScript.
- Who is it for?
- Adopt Rhai when you are already writing Rust and need user-supplied or machine-generated scripts to call into your own types and functions, with operation limits and call-stack caps available from the engine. Do not adopt it if your requirement is to run existing JavaScript, Python or Lua code, because Rhai is its own language with its own syntax.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 1, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What Rhai solves for a Rust application
Rhai is an embedded scripting language and evaluation engine for Rust, described by its README as a way to add scripting to any application. The audience is Rust developers who want part of their program to be configurable or extensible at runtime without recompiling the host binary. Typical shapes: a rules file that changes per deployment, a plugin surface for third parties, or code generated by a tool that the application then evaluates.
The language is deliberately close to JavaScript with Rust influences, and it is dynamically typed. That choice matters more than the syntax. A dynamically typed script can be written by someone who does not know your Rust types, and the engine resolves calls at runtime against whatever functions you registered. The README also states that all clonable Rust types can be passed into a script through an external Scope, with no special trait to implement. That is the integration point that separates Rhai from writing a small parser yourself: you keep your existing structs and expose only the functions and getters you choose.
The project is not a general-purpose runtime. It exists to be embedded. There is no standalone interpreter binary described in the README, and the homepage points at the crate page rather than a language download.
How the engine, Scope and AST fit together
The data flow has three pieces. A script is text. The engine compiles it to an AST. The AST is evaluated against a Scope, which is the set of variables visible to the script. The README recommends compiling once to AST form for repeated evaluations, which is the pattern to use when the same script runs on every request or every tick.
Native integration is the second half. The README lists tight integration with native Rust functions and types, including getters and setters, methods and indexers. So a script can call a method on a Rust value you passed in, and that call lands in your Rust code. Function overloading and operator overloading are supported, which means the same script-level name can dispatch to different Rust implementations based on argument types.
Evaluation has two back ends. The standard AST walker is the default. Rhai Grain is a bytecode compiler and VM, enabled with the grain feature, which the README describes as around 1.8 to 2x faster. The README's own single-core figure for the walker is 1 million iterations in 0.14 sec on a 2.6 GHz Linux VM running the repository's speed_test.rhai script. Treat that as the project's published number on its own hardware, not a prediction for your workload, and note that the speed test script is in the repository if you want to reproduce it.
Safety is enforced at the engine level rather than by the language. The README describes the engine as sand-boxed when declared immutable: it cannot mutate the containing environment unless explicitly permitted. It also lists protections against stack overflow, over-sized data and runaway scripts, plus a progress-tracking interface that lets the host terminate a script run. Those limits are configuration, not defaults you can assume.
Installing Rhai and running a first script
The crate is published as rhai on crates.io, and the README links to the crate page as the project homepage. Add it with cargo, which writes the dependency into your Cargo.toml.
cargo add rhaiThe repository's Cargo.toml gives the package version as 1.26.1 and rust-version as 1.66.0, so a toolchain older than that will not build it. The README also states the minimum Rust version as 1.66.0. If you prefer to edit the manifest by hand, the dependency line is a plain version entry:
[dependencies]
rhai = "1.26.1"The repository ships an examples directory with a hello.rs file, alongside arrays_and_structs.rs, custom_types.rs, custom_types_and_methods.rs, simple_fn.rs and strings.rs. Those names map to the first things you will want to try: printing, passing a Rust value in, and registering a function. The README's own description of the basic loop is to compile a script to AST and then evaluate it, so a first program follows that order: create an Engine, compile the script text, and call eval on the AST.
Optional capabilities are feature-gated. The README names grain for the bytecode VM, sync to make the engine Send + Sync, and serde for serialization and deserialization. Minimal builds are supported by excluding unneeded language features, which is documented under the start/builds/minimal page in the book. The book itself lives at rhai.rs/book and is the reference for the language and engine APIs; the README links individual chapters such as engine/compile.html and engine/scope.html.
Where Rhai is the wrong choice
The clearest boundary is language compatibility. Rhai is not JavaScript, Python or Lua. If your users already have scripts in one of those languages, Rhai cannot run them, and porting is a rewrite rather than a configuration change.
Panic behaviour is a stated guarantee rather than an observed one. The README frames it as a Don't Panic guarantee and says any panic is a bug, which tells you the project treats host panics as defects. It does not tell you that a script cannot exhaust your process in other ways. The same README notes that there is some unsafe code, added for performance reasons, so the crate is not a no-unsafe-code dependency. If your policy forbids unsafe in the dependency tree, that is a real constraint rather than a theoretical one.
Thread safety is opt-in. The README says a re-entrant scripting engine can be made Send + Sync via the sync feature. Without that feature you should not assume the engine can be shared across threads, and the README does not present sync as the default.
The evaluation cost is also worth stating plainly. The published figure is for a synthetic loop of 1 million iterations. Scripts that call back into Rust on every iteration will be dominated by the boundary crossing, not by the walker, and neither the README nor the Cargo.toml offers a number for that case.
Rhai compared with Lua and with a hand-written parser
Lua is the traditional answer to the same problem in C and C++ applications, and the difference is mostly about the host language. A Lua binding gives you a mature language with a large existing body of scripts and a C API. Rhai gives you a Rust crate whose types map onto Rust values directly, with no special trait to implement for clonable types, so passing your own structs in does not require a binding layer you maintain yourself.
The second alternative is not another language but no language: parse a small config format and interpret it with your own code. That avoids a dependency entirely, and it is defensible when the scriptable surface is a handful of conditionals. It stops being defensible once you need functions, closures, modules or error reporting with positions. The README lists closures, function pointers with currying, dynamically loadable modules with an overridable resolution process, and a debugging interface. Reimplementing that set is a project, not a task.
A third comparison is against the language the README itself positions Rhai near. It describes Rhai as similar to JavaScript plus Rust, which is a description of syntax and typing, not of ecosystem. Nothing in the repository suggests compatibility with JavaScript tooling.
Maintenance, features and licence
The repository is not archived, and the last push was on 2026-09-21. Releases are spaced roughly quarterly in the recent history: v1.24.0 on 2026-01-19, v1.25.0 on 2026-05-24, v1.26.0 on 2026-08-25, with the workspace Cargo.toml already at 1.26.1. The CHANGELOG.md at the repository root is where the upgrade cost lives, and it is the file to read before moving a pinned version.
Upgrade cost is mostly feature flags and MSRV. The manifest pins rust-version at 1.66.0 and the README repeats that number, so raising it in a future release would be a breaking change for anyone on an older toolchain. Cargo.msrv.lock exists at the root, which suggests the minimum version is exercised separately from the main lockfile.
The dependency set is small and worth naming because it is part of the audit surface: smallvec, thin-vec, num-traits, once_cell, ahash, bitflags, smartstring, plus rhai_codegen from the workspace. Optional dependencies include hashbrown, libm, serde, serde_json, core-error and no-std-compat, the last pulled from a Git URL rather than crates.io, which is a detail to note if your build environment forbids Git dependencies.
Licensing is dual: the Cargo.toml declares MIT OR Apache-2.0, and the repository carries both LICENSE-MIT.txt and LICENSE-APACHE.txt. The Apache-2.0 option includes a patent grant, which is often the reason teams pick it over MIT. This is a description of the declared terms, not legal advice; your own counsel should review the combination with your distribution model.
Editorial conclusion
Adopt Rhai when you are already writing Rust and need user-supplied or machine-generated scripts to call into your own types and functions, with operation limits and call-stack caps available from the engine. Do not adopt it if your requirement is to run existing JavaScript, Python or Lua code, because Rhai is its own language with its own syntax. Before committing, verify the exact feature flags you need in the Cargo.toml of the release you pin, and check whether you need the sync feature, since that changes the engine's thread-safety guarantees and is not enabled by default.
Frequently asked questions
Does Rhai run JavaScript or Python scripts?
No. Rhai is its own language, described in the README as similar to JavaScript plus Rust with dynamic typing. Existing JavaScript or Python scripts would need to be rewritten.
Can Rhai be used in a no-std or WebAssembly build?
The README lists WebAssembly and no-std among the supported targets, and the crate categories include no-std, embedded and wasm. Minimal builds are supported by excluding unneeded language features.
Is the Rhai engine safe to run untrusted scripts?
The README describes the engine as sand-boxed when declared immutable, and lists protections against stack overflow, over-sized data and runaway scripts, plus progress tracking to terminate a run. These are engine settings rather than behaviour you get without configuring them.
How do I make a Rhai engine shareable across threads?
The README states that a re-entrant scripting engine can be made Send + Sync via the sync feature. That feature is not presented as the default.
What is the minimum Rust version for Rhai?
Both the README and the workspace Cargo.toml give the minimum Rust version as 1.66.0.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/rhaiscript-rhai)