Tuesday, October 13, 2026

Scripting Rust? Rhai, Rune, Dyon, PyO3

 The premise you are highlighting is one of the most actively discussed topics in modern software engineering. Python won the world by being the "glue" language for C/C++, but as you noted, its 1990s-era design choices (the Global Interpreter Lock, dynamic typing, slow execution, fragmented packaging) created a massive maintenance overhead for modern codebases.

Today, Rust has largely replaced C/C++ as the modern systems language of choice. It offers unmatched memory safety and performance, but its strict compiler (specifically the borrow checker) makes it notoriously slow for rapid prototyping, UI scripting, or high-level business logic.

If a "Python-for-Rust" were created from scratch today, it would look distinctly different from 1990s Python, and several active projects are already trying to claim this exact throne.

What would a "Modern Python" look like today?

If we built a high-level scripting language today explicitly to wrap and script Rust modules, it would likely have these core characteristics:

  1. Strongly Typed with Aggressive Inference: Instead of Python's purely dynamic typing (which requires massive test suites to catch runtime typos), it would use a TypeScript-style or Rust-style type system. You wouldn't have to write out types everywhere, but the compiler would infer them and catch errors before the script runs.

  2. Data-Race Free Concurrency (No GIL): A modern language wouldn’t have a Global Interpreter Lock. It would feature native async/await and lightweight threads (like Go's goroutines or Erlang's actors) that map efficiently to multi-core processors.

  3. Rust-Like Error Handling: Instead of Python’s try/except blocks—where any function can crash unexpectedly—it would use Rust’s Option and Result types, combined with pattern matching. This forces the scripter to acknowledge that a function might fail, but with a highly ergonomic, readable syntax.

  4. Zero-Cost Interoperability: It would have a first-class Foreign Function Interface (FFI) for Rust. You would be able to import a Rust crate directly into your script, and the language would automatically understand Rust’s structs, enums, and traits without needing manual C-bindings.

  5. A "Cargo-like" Toolchain: It wouldn't rely on the chaotic pip/venv/conda ecosystem[1]. It would come with a single, unified tool (like Rust's cargo or JavaScript's deno) that handles formatting, linting, testing, and package management out of the box.


Known Efforts and Languages Filling this Niche

The software community is tackling this exact problem right now from three distinct angles:

1. Embedded "Rust-Native" Scripting Languages

There are new scripting languages built explicitly in Rust, for Rust, designed to be embedded in Rust applications. They abstract away the painful "borrow checker" while keeping the performance and safety.

  • Rhai: Often described as "the Lua of Rust"[2][3]. Rhai is a dynamic, embedded scripting language[4] that uses a syntax very similar to Rust/JavaScript but completely removes the concept of lifetimes and borrowing[5]. It is incredibly easy to expose Rust functions to Rhai scripts[6][7].

  • Rune: An embeddable dynamic language for Rust that is "async-first."[5] It natively supports modern asynchronous programming and macros, allowing developers to script high-performance async Rust backends without wrestling with Rust's strict compiler rules[8].

  • Dyon: A dynamically typed scripting language written in Rust that uses a "lifetime checker" instead of a garbage collector. It’s an experiment in making a scripting language that feels structurally like Rust but without the cognitive overhead[9].

2. Evolving Python Itself via PyO3

Ironically, the most successful "Python-for-Rust" language is simply... Python.
Because Rust is so safe and has such incredible macro capabilities, a project called PyO3 (and its build tool, Maturin) has made it completely frictionless to write Rust code and expose it to Python.
Instead of inventing a new language, the ecosystem is rapidly rewriting Python's heavy lifting in Rust. Modern Python staples like Polars (dataframes), Pydantic (data validation), and Ruff (linting) are just Rust programs cleverly wearing a Python trench coat. Rust is effectively saving Python from its own performance bottlenecks[4].

3. The "Python but Fast" Languages (Systems-Level Python)

There are active attempts to completely rebuild Python's syntax from the ground up to achieve C/Rust-level performance without needing a second language underneath:

  • Mojo: Created by Chris Lattner (the creator of LLVM and the Swift programming language), Mojo is a superset of Python. It aims to have the exact same syntax as Python but allows developers to gradually drop down to systems-level memory management and strong typing. While its current focus is AI and the C++ ecosystem, conceptually, Mojo is exactly what "Python created today" looks like.

  • Erg: A statically typed language that compiles down to Python bytecode[10]. It looks like a modern, functional Python but provides the strictness and safety guarantees of a language like Rust[10], running perfectly alongside existing Python libraries.

Summary

If Python were invented today to script Rust, it would look a lot like TypeScript mixed with Rust's error handling, running on a lightweight garbage collector or automatic reference counting, without a GIL. While projects like Rhai and Rune are attempting to be exactly this[7][9], the pragmatic reality is that tools like PyO3 have made Rust and Python such good friends that the industry is simply using them together to get the best of both worlds.

Here are the GitHub links to all the projects mentioned, categorized by how they approach the problem:

1. Embedded "Rust-Native" Scripting Languages

These are the languages built in Rust specifically to act as high-level scripting layers for Rust applications:

2. Evolving Python Itself via Rust Interop

These tools allow you to use standard Python as your scripting language while doing the heavy lifting in Rust, along with examples of massive projects doing exactly this:

Examples of "Python as a wrapper for Rust":

3. The "Python but Fast/Strict" Languages

These are the languages trying to reinvent Python's syntax for the modern, systems-level era:

  • Mojo: https://github.com/modularml/mojo
    (Chris Lattner's project to build a strict, systems-level superset of Python. The core compiler is proprietary to Modular, but the standard library and issue tracking are hosted here on GitHub).

  • Erg: https://github.com/erg-lang/erg
    (A statically typed language that compiles to Python bytecode, giving you Rust-like strictness while running alongside normal Python.)


Because Python is written in C (specifically CPython), it has always had an API that allows you to write C code and expose it to Python. PyO3 is a framework that interacts with that C API so you can use Rust instead.

PyO3 allows you to do two main things:

1. Write Python modules in native Rust (The main use case)

This is exactly what you guessed. You write high-performance, strictly-typed code in Rust, and PyO3 compiles it into a standard Python extension module (a .so file on Linux/Mac, or a .pyd file on Windows).

To a Python user, it just looks like a normal Python library. They can run import your_module, but under the hood, the code executing is blazing-fast Rust.

Here is a simplified example of how easy PyO3 makes this look in Rust:

Rust
use pyo3::prelude::*;

// 1. Write a normal Rust function, and add the PyO3 macro above it
#[pyfunction]
fn add_numbers(a: usize, b: usize) -> PyResult<usize> {
    Ok(a + b)
}

// 2. Register it as a Python module
#[pymodule]
fn my_rust_module(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(add_numbers, m)?)?;
    Ok(())
}

Once compiled (usually using the tool Maturin), a Python developer can just do this:

Python
import my_rust_module

# This is actually calling the compiled Rust code!
result = my_rust_module.add_numbers(5, 7) 
print(result)

Why is this so powerful?

  • Speed: If you have a massive loop or heavy math in Python, you can move just that specific function into Rust and make it 10x to 100x faster.

  • Bypassing the GIL: PyO3 allows you to temporarily release Python's Global Interpreter Lock (GIL). You can pass data to Rust, let Rust spawn 16 threads to process it simultaneously, and then hand the result back to Python.

  • Safety: Unlike writing C extensions (which often crash Python with memory leaks or segfaults if you make a mistake), Rust's compiler guarantees memory safety.

2. Embed Python inside a Rust application

The second (though less common) thing PyO3 does is the exact reverse: it allows a Rust program to spin up a Python interpreter inside itself.

If you are building a complex backend system or game in Rust, but you want to allow your users to write simple Python scripts to create plugins or automate tasks, PyO3 allows your Rust application to read, execute, and pull data out of Python scripts.

Summary

PyO3 is the secret weapon of the modern Python ecosystem. When people say tools like Polars or Pydantic are "written in Rust," what they actually mean is they are written in Rust using PyO3, which allows Python developers to easily pip install and use them without even knowing Rust is running under the hood.

1. Are they embedded or standalone?

Actually, all three (Rhai, Rune, AND Dyon) are designed to be embedded into Rust.
While they all provide a Command Line Interface (CLI) that allows you to run them as standalone scripts if you want to, their primary purpose is to be deeply integrated into a Rust host application.

  • Rhai & Rune: Designed as general-purpose embedded scripting languages for any Rust backend (web servers, data processors, automation tools).

  • Dyon: Designed specifically to be embedded into game engines (it was created by the developers of the Piston game engine for Rust). It includes native math features (like 4D vectors and hex colors) tailored for game development.

2. Are they interpreted or compiled?

You are correct that none of them compile down to raw machine code (AOT) the way Rust, C++, or Go do. However, how they execute under the hood is slightly different, and mirrors the divide in mainstream scripting languages:

  • Rhai (AST Walker): Rhai is interpreted. When you pass a script to Rhai, it compiles it into an Abstract Syntax Tree (AST), and then a Rust engine "walks" that tree to execute the logic. (Note: Rhai recently added an experimental bytecode VM called "Rhai Grain," but AST walking is its bread and butter).

  • Rune (Bytecode VM): Rune is technically compiled to bytecode, which is then run on its own highly optimized Virtual Machine (the Rune VM). If you know how standard Python (CPython) or Java works, Rune works the exact same way. It compiles scripts to intermediate bytecode first, which gives it a performance edge and excellent async support.

  • Dyon (AST Walker): Dyon is interpreted. What makes Dyon incredibly unique, however, is that it does not have a Garbage Collector. To prevent memory leaks, it borrows a concept from Rust and uses a runtime "lifetime checker" to track variables.

Why design them this way?

If they aren't compiled to raw machine code, won't they be slow?

Yes, they are much slower than pure Rust. But that is the deliberate trade-off. Game developers and backend engineers use Rhai, Rune, and Dyon because they provide instant hot-reloading.

If you write your whole game in Rust, every time you tweak the jump height of a character, you might have to wait 30 seconds for the Rust compiler to rebuild the game. If you expose the jump height to a Dyon or Rune script, you can change the jump height in a text editor and see the character jump higher in the game instantly, without ever recompiling the Rust engine underneath.

1. Can they be extended to serve as a DSL?

Yes, and this is one of their biggest selling points. Because Rust itself has a legendary macro system, the languages built on top of it have inherited highly advanced ways to mold their syntax.

  • Rhai (The Ultimate DSL Builder):
    Rhai is explicitly designed to be stripped down and molded into a DSL. In fact, "Use Rhai as a DSL" is a dedicated chapter in their official book.

    • Disable Everything: You can surgically disable standard features. Don't want users to be able to write while loops, use floating-point math, or define variables? You can turn all of that off.

    • Custom Operators & Syntax: You can define entirely new operators (e.g., registering # or => to do custom math) or write custom syntax parsers that allow your script writers to write domain-specific commands that Rhai evaluates behind the scenes.

  • Rune (Macro-Powered DSLs):
    Rune approaches DSLs exactly like Rust does: Macros. Rune natively supports token-stream macros. This allows you to define custom structural blocks (like html! { <div> </div> } or query!(SELECT * FROM users)) directly inside the script. This makes it incredibly powerful for writing declarative DSLs while still having a fully-featured programming language wrapped around it.

  • Dyon (A Pre-built Game DSL):
    Dyon is less about building your own DSL, and more that it is a DSL for math and game programming. It has built-in syntax for 4D vectors (e.g., vec4(x, y, z, w)), HTML hex colors (e.g., #ff0000), and mathematical linkings that make it highly domain-specific out of the box.

2. Can these extensions run on WASM?

Yes, absolutely. Because Rust treats WebAssembly (wasm32-unknown-unknown and wasm32-wasi) as a first-class citizen, languages written in pure Rust inherit this portability flawlessly.

  • Rhai on WASM:
    Rhai compiles perfectly to WebAssembly. Because it can be compiled as a no_std (no standard library) crate, it is completely independent of the operating system. A full-featured Rhai scripting engine compiles to a tiny WASM payload of less than 400KB[1]. This means you can embed Rhai into a Rust web backend, compile the whole thing to WASM, and run the exact same scripts directly in the user's web browser using JavaScript.

  • Rune on WASM:
    Rune also has exceptional WASM support. In fact, if you go to Rune's website and use their "Playground" to test code, that is a full Rune Virtual Machine running entirely in your browser via WebAssembly[2]. It is also being heavily used in the "TinyML" space to compile Machine Learning pipelines that execute inside WebAssembly edge runtimes[3].

Summary

If your goal is to build a highly restrictive, custom-syntax DSL for a specific business domain, and you need it to run on the web via WASM, Rhai is practically tailor-made for your use case[1][4].

If you want a more fully-featured Rust-like language but want to use Macros to define DSL blocks (and still run it in WASM), Rune is your best bet[2][5].




No comments: