RAWRPre-alpha

//A scripting language that reads like Rust

Rust Augmented With Rust

Rawr has Rust’s structs, enums, traits and match, checked up front with rustc-style errors. Your program stays Rust; the parts you want to change while it runs are written in Rawr.

The rawr crate is the language, and any Rust program can embed it. A shell gives it a domain. The first one, rawr-iced, runs native iced apps that reload in about 0.2 s.

drag the divider
src/main.rsRust
enum Item {
    Book { price: f64 },
    Coffee { cups: i64 },
}

trait Priced {
    fn price(&self) -> f64;
}

impl Priced for Item {
    fn price(&self) -> f64 {
        match self {
            Item::Book { price } => *price,
            Item::Coffee { cups } => 3.5 * (*cups as f64),
        }
    }
}

fn total(items: &[Item]) -> f64 {
    let mut sum = 0.0;
    for item in items {
        sum += item.price();
    }
    sum
}

fn main() {
    let cart = vec![Item::Book { price: 20.0 }, Item::Coffee { cups: 2 }];
    println!("total: {}", total(&cart));
}
cargo runtotal: 27
Spot the difference. The enum, the trait, the impl and fn total are the same bytes. Only the ending differs: Rust needs a fn main that prints, while a Rawr script runs its statements from the top and hands its last expression back to the Rust program that evaluated it. Left: a complete Rust 2024 program; stable rustc compiles it without warnings and it prints total: 27. Right: the SCRIPT string in examples/cart.rs, which cargo run --example cart evaluates to 27.

//Shells

A language, then a shell for a domain

The language core knows nothing about windows or widgets. It parses, checks and runs Rawr inside whatever Rust program embeds it.

A shell gives it a domain: the types, the commands and the reload loop. iced is the first shell; rawr-iced is Rawr for iced apps.

rawrThe language. Embed it in any Rust program with Engine::new().eval. Builds on stable Rust 1.91 or newer.
rawr-icedThe first shell, for iced apps: reload on save in about 0.2 s, rustc-style errors, your Rust crates imported with no bindings, #[preview] views and PNG snapshots, and the ?play bot and playground.
examples/cart.rsthe host
use rawr::Engine;

const SCRIPT: &str = r#"
… the Rawr script above …
"#;

fn main() -> Result<(), Box<rawr::EvalAltResult>> {
    let total = Engine::new().eval::<f64>(SCRIPT)?;
    println!("total: {total}");
    Ok(())
}
cargo run --example carttotal: 27

//Install

Install from git

The language is an ordinary crate. It builds on stable Rust 1.91 or newer.

The iced shell is more demanding. It runs on Apple Silicon Macs for now and needs one pinned Rust nightly, nightly-2026-07-11, with the rustc-dev and rust-src components. Rawr builds iced and your own crates with that toolchain too.

Embed the language

cargo add --git https://github.com/airstrike/rawr rawr

Then Engine::new().eval::<T>(script), as in examples/cart.rs above.

The iced shell: toolchain, CLI and import driver

rustup toolchain install nightly-2026-07-11 --component rustc-dev,rust-src
cargo +nightly-2026-07-11 install --locked --git https://github.com/airstrike/rawr rawr-cli
cargo +nightly-2026-07-11 install --locked --git https://github.com/airstrike/rawr rawr-import --features rustc-driver

rawr-cli installs rawr. rawr-import installs rawr-import-driver next to it, which reads your Rust crates for imports. The first rawr run compiles iced, so it takes a few minutes; later launches reuse that build.

Start an iced project

rawr new hello && cd hello && rawr run

rawr new writes one file, src/main.rawr, a counter. There is no manifest until you want one. Edit it while the window is open.

//The iced shell

Write iced. Reload it like a script.

rawr-iced runs native iced apps written in Rawr, with the same impl Widget<Message>, the same column![], the same match, against iced master.

Save a .rawr file and rawr run reloads it in about 0.2 s. The window stays open and keeps its state.

drag the divider
src/main.rsRust
use iced::Center;
use iced::widget::{Widget, button, column, text};

pub fn main() -> iced::Result {
    iced::run(Counter::update, Counter::view)
}

#[derive(Default)]
struct Counter {
    value: i64,
}

#[derive(Debug, Clone, Copy)]
enum Message {
    Increment,
    Decrement,
}

impl Counter {
    fn update(&mut self, message: Message) {
        match message {
            Message::Increment => {
                self.value += 1;
            }
            Message::Decrement => {
                self.value -= 1;
            }
        }
    }

    fn view(&self) -> impl Widget<Message> {
        column![
            button("Increment").on_press(Message::Increment),
            text(self.value).size(50),
            button("Decrement").on_press(Message::Decrement)
        ]
        .padding(20)
        .align_x(Center)
    }
}
cargo run
Spot the difference. On the left is iced’s own counter example. On the right is the same file saved as main.rawr. The diff is the file extension and the command that runs it. Left: examples/counter from iced master at be88873d, with its test module left out. Right: those exact bytes, which rawr check accepts unchanged.

//iced shell · Commands

One command for each part of the loop

Everything works on a project directory or a single file. Run rawr --help for the full grammar.

rawr runOpens the app. Saving a .rawr file reloads it in place; changing your Rust crates rebuilds them natively.
rawr checkType-checks without a window and reports errors the way rustc does.
rawr check --as-rustCopies the project under target/ with .rawr renamed to .rs and runs cargo check on it.
rawr previewOpens a view marked #[preview] on its own. ← and → step through a group.
rawr snapshotRenders every preview headlessly to PNG; --check compares them with the committed ones, --update accepts new ones.
rawr mcpAn MCP server that lets a coding agent list and render previews, run checks and screenshot the running window. rawr init --claude --codex registers it.
rawr doctorReports on the environment and the project.

//iced shell · Diagnostics

Errors you already know how to read

Delete the Decrement arm from the counter and rawr check answers the way rustc would: the code, the span, and what is missing.

rawr checkexit 1
error[RAWR-RESOLVE-MATCH]: invalid match for `Message`: non-exhaustive patterns; missing Message::Decrement
 --> src/main.rawr:21:9-25:9
 21 |         match message {
    |         ^^^^^^^^^^^^^^^
    | ...
 25 |         }
    | ^^^^^^^^

//iced shell · Rust imports

Your own Rust crates, with no bindings

List a crate in rawr.toml and use it. Rawr reads only the items your code touches, so a custom native widget in a Cargo crate is just another widget.

This is gallery_ui::Dial from RawrGallery, an ordinary iced Widget written in Rust and driven from dial.rawr.

rawr.toml
[rust.dependencies]
gallery_ui = { package = "gallery-ui", path = "rust/gallery-ui" }
src/dial.rawr
use gallery_ui::Dial;

let face = container(Dial::new(self.value, Message::Turn).range(0..=100))
    .padding(LOOSE)
    .style(inset);
RawrGallery's Dial page in the Ink sheet: a square dial at 20, readouts for position and band, and Reset and Hide detail buttons
The Dial page, rendered by rawr snapshot.

//iced shell · ?play

Render a snippet in Discord

In a Discord channel where the bot is turned on, write ?play over a code block with a view or a whole app, and it replies with the frame. The playground does the same in a browser, nothing to install.

The iced counter in its default dark theme: an Increment button, the number 1 and a Decrement button
Discord
?play 160x170 msg=Message::Increment
```rust
use iced::Center;
use iced::widget::{Widget, button, column, text};
// … the rest of the counter
```

The iced counter above after one Increment, as rawr-play, the renderer behind the bot, draws it.

//Status

Pre-alpha, plainly

Rawr is early. The language crate builds on stable Rust. The iced shell runs on Apple Silicon Macs, follows iced master, and pins one nightly. APIs, commands, file formats and error codes will change without notice.

It covers a lot of Rust but not all of it. When Rawr rejects valid Rust that it claims to cover, that is a bug, and a small reproduction is the most useful report.