Rhai is a simple embedded scripting language for Rust. It provides a safe and efficient way of adding scripts to your Rust applications. The syntax of Rhai is closely related to JavaScript, making it easier in terms of picking it up relatively quickly.
In this article, we are going to be taking a look at Rhai and build a very simple game using Rust and Rhai together.
If you are new to Rust, you can take a look at our archives here to find a wealth of information on all of the basics of Rust development that might be of interest to you as a starting place.
That being said, let’s proceed with the article and take a quick look at Rust before learning how Rhai works with it.
Jump ahead:
Rust is an extremely popular language today. It has been consistently voted by developers as the most loved programming language for several years now. In spite of its steep learning curve, once you get to grips with it, Rust offers a lot of benefits over other languages such as security, maintainability, and scalability.
Many companies have been adopting Rust as part of their technology stack. Rust also has a strong community backing it, making it a great choice if you’re looking for a scalable and stable programming language for the long-term.
Now that you know what Rust is, let’s look at Rhai and what it can do for you.
Rhai, also called RhaiScript, is a fast and embeddable scripting language. Its core purpose is to enable you to write scripts within your Rust applications.
The name “Rhai” comes from Chai, a scripting language for the C++ language. Similar to Rhai, Chai can be used to write scripts with C++. There are also other scripting languages like Lua, which can work with multiple languages, like C and Python.
A major advantage of using Rhai is its simple syntax. Unlike Rust, which has a relatively steep learning curve as I mentioned, Rhai is syntactically similar to JavaScript.
This makes Rhai very useful when building large-scale Rust applications. You can even write complicated algorithms in Rust using Rhai, making it an ideal choice when it comes to writing scripts for Rust.
Additionally, Rhai is memory safe compared to other scripting languages, like JavaScript. One of the core principles of Rust is memory safety, so Rhai is always run in a controlled environment — Rhai scripts cannot change any of the values in its environment and always runs in a sandbox.
Some of the other features that make Rhai very attractive to developers are:
Now that you know what Rhai can do, let’s look at a few examples of Rhai scripts.
If you want to follow along, you can use the Online Playground to try some scripts yourself. To install and run Rhai on your system, you can follow the installation instructions here.
First, let’s write a hello world function using Rhai.
To use Rust with Rhai, you have to create an instance of the scripting engine. You can then write your Rhai script and execute it by calling the run() function.
use rhai::{Engine, EvalAltResult}; pub fn main() -> Result<(), Box<EvalAltResult>> { let engine = Engine::new(); // create an instance of "Engine" let script = "print('Hello World');"; // one line Rhai script stored in a variable engine.run(script)?; // Run the script Ok(()) }
If you look at the above example, we invoke an instance of “Engine” to run the hello world script. But the best way to run Rhai scripts is to have your code in files and then call them from your Rust function.
Here is another example of the hello world code executed from a Rhai script file. First, write your Rhai code in a .rhai file.
// fiename: helloworld.rhai print("Hello world");
Then, you can call your Rhai file from your Rust function using the run_file or eval_file methods.
// filename hello.rs use rhai::{Engine, EvalAltResult}; pub fn main() -> Result<(), Box<EvalAltResult>> { let engine = Engine::new(); // create an instance of "Engine" engine.run_file("helloworld.rhai".into())?; // run the rhai script Ok(()) }
Now let’s try a simple game using Rust and Rhai. We will write a guessing game that generates a random number between 1 and 100, and the user will keep guessing till they get it right.
We will use cargo to initiate a project. Cargo is a package manager for Rust, similar to npm for Node.js. It helps us to create new projects and manage dependencies in our project in addition to a few other useful functions.
(Note: Here is the complete source code of the project if you want to follow along)
$ cargo new guessing_game Created binary (application) `guessing_game` package $ cd guessing_game $ ls Cargo.toml src
The Cargo.toml file contains the dependencies for your project. In this project, we will use two dependencies — Rand and Rhai.
We need the Rand module to generate a random number. And the Rhai module is used for invoking the Rhai engine to run our script.
Now that we have setup our project, we can start writing the code. Here is how our game logic will work:
In this guessing game, we will delegate the conditional flow to Rhai. We will get the input from the user and send the input along with the target number to Rhai to tell us if the guess is greater than, lesser than, or correct.
Before we start writing our script, let’s understand the concept of scopes. In Rust, you can create a scope and add values to it. This scope can then be sent to our Rhai script, which will have access to the values in the scope. This is important to understand since Rhai cannot directly change any values in our Rust program due to memory-safety constraints.
So, let’s write a Rhai script with a function that takes in one parameter, the guess. We will create a scope in Rust and add the original target number and send it to Rhai during runtime.
// filename: guess.rhai fn guess(input) { if input == target{ print(`Congrats! You Won!!!`); return true; } else if input > target{ print(`INPUT TOO BIG`); return false; } else{ print(`INPUT TOO SMALL`); return false; } }
Now, let’s call this script using Rust. We will place the user input prompt inside a loop and use Rhai to check if they have guessed the correct number. We will send the user feedbacks until they guess the correct number.
In the previous example, we saw the engine.run_file function to run a script. A better approach is to compile your script first and then call the functions within your script. For Rhai, we use the AST compiler. AST compilers are out of scope for this article, so if you want to learn more, here is a great resource for you to take a look at.
// src/main.rs use rhai::{Engine, Scope, EvalAltResult}; use rand::Rng; use std::io; pub fn main() -> Result<(), Box<EvalAltResult>>{ let engine = Engine::new(); //invoke the RHAI engine let mut scope = Scope::new(); // create a new scope let target = rand::thread_rng().gen_range(1..=100); // generate a random number between 1 and 100 scope.push("target", target); // adding the target number to scope for Rhai // use ast compiler to compile the script let ast = engine.compile_file("guess.rhai".into())?; println!("{}","Guess the number!"); // loop till the user guesses the correct number loop{ let mut input = String::new(); // variable to get user input // get the input from user io::stdin() .read_line(&mut input) .expect("Failed to read line"); // convert the input from string to number let guess: i32 = input .trim() .parse() .expect("Wanted a number"); // call the guess function from the compiled script and pass scope along with the input number. Returns a boolean value. let result = engine.call_fn::<bool>(&mut scope, &ast, "guess", ( guess,) )?; // if "true" is returned, break out of the loop if result { break }; } Ok(()) }
You can see that we have invoked an instance of the Rhai engine, followed by creating a scope. This is followed by generating a random number between 1 and 100 and pushing it to the scope.
We then compile our Rhai script to be used for calling the “guess” function. Now, we can create a loop, get the input from the user and run the logic using the Rhai script.
The script returns true if the guess is correct. This is used to break out of the loop. We use the engine.call_fn method to pass the scope, function name, and the function parameters in a tuple.
Now, lets run our script. We can use “cargo run” command to build and run our Rust program.
$ cargo run
Congrats! You have written your first game using Rust and Rhai. You can also use Rhai for implementing more complex scripts and algorithms that may be harder to write in Rust.
So far we have seen a lot about Rhai and writing scripts using Rhai. Now, let’s look at some of the downsides of Rhai.
Rhai is not the only scripting language for Rust. Let’s look at a few other options you may wish to consider.
Gamelisp is a great alternative to Rhai if you are looking for a scripting engine just for writing games. Gamelisp integrates with Rust easily and has more features than Rhai for writing games. This includes garbage collection, memory safety, and seamless integration with the Rust api.
Throne is another useful game scripting language for Rust. It offers rapid prototyping and story logic features, making it a great choice when writing games in Rust. Throne is relatively unpopular compared to Rhai, but is steadily gaining a developer community.
Dyon is another dynamically typed scripting language for Rust. Dyon is packed with features including 4D vectors, macros, and dynamic modules. It is a great choice if you are looking to write simple games and interactive programming environments.
Rhai is a fantastic scripting language for Rust. It offers a simple scripting experience for developers when working with an intricate language like Rust. From writing complex algorithms to simple games, Rhai is a great tool to work with.
Rhai also has it challenges like any scripting language. It is designed to work with Rust, so it does not offer the full capabilities you may have come to expect from working with languages like JavaScript. That being said, Rhai is an excellent choice of scripting language when building applications with Rust.
Debugging Rust applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking the performance of your Rust apps, automatically surfacing errors, and tracking slow network requests and load time, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your Rust application. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app’s performance, reporting metrics like client CPU load, client memory usage, and more.
Modernize how you debug your Rust apps — start monitoring for free.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to implement one-way and two-way data binding in Vue.js, using v-model and advanced techniques like defineModel for better apps.
Compare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.