Rust has been something of a rockstar language in recent times as it continues to grow in popularity, thanks to its numerous benefits; like being very close to C/C++ in terms of performance and its ability to compile to WebAssembly.
The current state of gaming industry has primarily focused on programming languages such as C/C++, Java, and C# for both development and for game engines.
That was until the rise of Rust, which came with a different approach insofar as creating suitable programming languages that are flexible enough for games with respect to high performance, interfacing with other languages, and much more.
I will walk you through eight scripting languages developed for gaming in Rust which are categorized based on the following:
GameLisp is a scripting language that has a unique garbage collector designed to run once per frame without causing latency spikes.
It works by loading the glsp file with the .glsp
extension in Rust code. GameLisp is used for building 2D games as it also comes with common features available in modern languages.
Features include the following:
GameLisp was developed to handle garbage collection; this way it’s able to collect or gain memory back which has been allocated to objects.
GameLisp is a Rust crate that can be installed and instantiated in a Rust program by loading the GameLisp file — this process makes it easy for GameLisp to be integrated in Rust code.
GameLisp doesn’t implement unsafe code since the core logic of GameLisp is entirely written in Rust with few dependencies.
The other benefit of GameLisp is the object system that is built around mixins and state machines specifically for game development entities.
Example of GameLisp:
# GameLisp (let fibs (arr 0 1)) (while (< (len fibs) 31) (push! fibs (+ \[fibs -1\] [fibs -2]))) (prn [fibs -1]) ;
// Rust use glsp::prelude::*; fn main() { let app = Runtime::new(); app.run(|| { glsp::load("external.glsp")?; Ok(()) }); }
Throne is a scripting language for game prototyping and story logic; in other words, Throne is used to create syntax rules for a game which can be embedded in other engines.
It is developed to interface with JavaScript via WebAssembly. Throne can be used for storytelling and prototyping the concept of a game. It has the file extension .throne
.
Its features includes:
Throne prototyping uses a variety of contexts, including semantics, syntax, etc., to express and evaluate the precision of game development analysis.
Logic in storytelling is based on a narrative, character’s actions, events, etc., of a user’s point of view for a particular game.
Example of Throne:
// Throne file // initialize state fall-time 5 . default-fall-time 5 . block-id 0 . gravity-shape-id 0 . max-width 10 . max-height 20 #update . !shape _X _Y _BLOCKS . gravity-shape-id ID . + ID 1 ID' = gravity-shape-id ID' . #new-shape #new-shape . $max-height H = new-shape 4 H ((block -1 0) ((block 0 0) ((block 1 0) ((block 0 1) (cons))))) . #shape-to-blocks (#input-lr)
// Rust #[cfg(not(target_arch = "wasm32"))] fn main() { let mut context = throne::ContextBuilder::new() .text(include_str!("blocks.throne")) .build() .unwrap_or_else(|e| panic!("{}", e)); }
Wgpu is a cross-platform Rust graphics WebGPU standard API that runs on Vulkan, Metal, D3D12, D3D11, and OpenGLES — and likewise on WebGPU via WebAssembly (wasm).
It is built to allow web code access to GPU functionality in a safe and reliable paradigm, which also has binding support for languages via wgpu-native.
The features include:
Wgpu comes with WebGL as its core standard API, which is also used for rendering graphics such as 2D and 3D on a web browser without external plugins.
The performance of Wgpu solely relies on a GPU (graphics processing unit) abstract layer designed to augment the rendering process of graphics.
Example of Wgpu:
// wgsl file @vertex fn main_vs( @location(0) particle_pos: vec2<f32>, ) -> @builtin(position) vec4<f32> { let angle = -atan2(particle_vel.x, particle_vel.y); let pos = vec2<f32>( position.x * cos(angle) - position.y * sin(angle), position.x * sin(angle) + position.y * cos(angle) ); return vec4<f32>(pos + particle_pos, 0.0, 1.0); } @fragment fn main_fs() -> @location(0) vec4<f32> { return vec4<f32>(1.0, 1.0, 1.0, 1.0); } fn main() { let draw = device.create_shader_module(&wgpu::ShaderModuleDescriptor { label: None, source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("draw.wgsl"))), }); }
Mlua is a high-level binding for the Lua language to provide a safe and flexible API. It has the following features:
This allows other Lua versions API modules to be accessed in Rust as well as Lua in a standalone environment.
It enables async/await support in which executors like Tokio and asyn-std can be used.
Support for serialization and deserialization that support mlua
types with Serde.
The standalone mode is the declaration of Lua’s code directly in the Rust program, without the need for external import.
Example of Mlua:
fn main() { let mut lua = hlua::Lua::new(); lua.execute_from_reader::<()>(File::open(&Path::new("script.lua")).unwrap()) lua.set("add", hlua::function2(add)); lua.execute::<()>("local c = add(2, 4)"); // calls the `add` function above let c: i32 = lua.get("c").unwrap(); //standalone mode lua.set("a", 12); let val: i32 = lua.execute(r#"return a * 5;"#).unwrap(); }
Dyon is a dynamically-typed language designed for game engines and interactive applications with the extension .dyon.
It is designed to work around limited memory model because it lacks a garbage collector. It has the following features:
This allows more flexibility with types which can be used in Rust.
This is a multi-threaded concept but with Go-like style which specifies go
before the function name.
It also comes set of macros which makes embedding code with Rust much easier.
Example of Dyon:
fn main() { shape := unwrap(load("src/shape.dyon")) game := unwrap(load(source: "src/game.dyon", imports: [spape])) call(game, "main", []) }
Ketos is a scripting language developed to provide an interface for accessing Lisp APIs. It is compiled into bytecode and interpreted by Rust code. It has the .ket
extension. with the following features:
Ketos supports the compilation of .ket files to bytecode, which improves performance during runtime.
It provides macro support for extracting arguments from Ketos values and value conversion from Rust.
It also provides support for modification of bytecode instructions by dissecting data into opcodes.
Example of Ketos:
//ket (define (range a :optional b (step 1)) (cond ((null b) (range 0 a)) ((> step 0) (range-pos a b step ())) ((< step 0) (range-neg a b step ())) (else (panic "`range` got 0 step"))))
// Rust fn main() { let interp = Interpreter::new(); interp.run_code(r#" (define (factorial n) (cond ((< n 0) (panic "factorial got negative integer")) ((<= n 1) 1) (else (* n (factorial (- n 1)))))) "#, None).unwrap(); }
Mun is an innovation of Lua JIT designed for productivity and speed. It is built to easily detect errors during compiling ahead of time (AOT), as opposed to being interpreted or compiled with JIT (just in time) in mind.
It has the .munlib
extension with the following features:
It is compiled ahead of time (AOT) by detecting errors in the code during this process; with this, developers can easily debug runtime errors on their IDEs (integrated development environment).
It resolves type check at compilation time instead of runtime which results in instant feedback for correction when not properly specified.
Ahead of time (AOT) with static typing and LLVM (low-level virtual machine) for optimization enforces Mun to be compiled to machine code and be executed cross-platform for optimal runtime performance.
It has support for IDE integrations for code completion and refactoring tools, which makes life easier for developers.
Mun offers support for compiling to all targeted platforms from any supported compiler.
Example of Mun:
// munlib extern fn random() -> i64; pub fn random_bool() -> bool { random() % 2 == 0 }
// Rust fn main() { let app = RuntimeBuilder::new("main.munlib") .spawn() .expect("Failed to spawn Runtime"); let app_ref = app.borrow(); let result: bool = invoke_fn!(app_ref, "random_bool").unwrap(); println!("random bool: {}", result); }
LuaJIT is developed to interface Lua code from Rust by easily accessing the functions that correspond directly to the underlying Lua C native-code API. It has the following features:
It supports other languages via native binding, since the native API is written in C.
LuaJIT also provides support for macro to easily communicate/interface directly with C.
Example of LuaJIT:
pub fn main() { let mut state = State::new(); state.open_libs(); state.do_string(r#"print("Hello world!")"#); state.push(lua_fn!(return_42)); state.set_global("return_42"); state.do_string(r#"print(return_42())"#); }
We’ve been able to look at several scripting languages for gaming in Rust, most of which are still in their infancy.
The most significant and popular one on this list is Wgpu, because it serves as the core of the WebGPU integration in Firefox, Servo, and Deno.
Elsewhere, Dyon serves as a dynamic language built with support for 4D vectors data type to augment 2D and 3D programming, mathematical iterations and Unicode symbols to improve readability and so much more. Others are built for interfacing with other platforms for performance optimization and more.
What Rust scripting language do you use for your game projects and why do you use it? I’d love to know in the comments section. Gracias! ☺️
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 nowSOLID principles help us keep code flexible. In this article, we’ll examine all of those principles and their implementation using JavaScript.
JavaScript’s Date API has many limitations. Explore alternative libraries like Moment.js, date-fns, and the new Temporal API.
Explore use cases for using npm vs. npx such as long-term dependency management or temporary tasks and running packages on the fly.
Validating and auditing AI-generated code reduces code errors and ensures that code is compliant.