Skip to content

Oxie is a concise, familiar language that compiles to plain Rust

It keeps Rust's ownership, traits and speed, and drops the bookkeeping. Every crate works, and programs run exactly as fast as hand-written Rust, because they are Rust.
main.oxi
error enum BankError {
Insufficient(needed: u64, available: u64) = "need $needed, have $available"
Frozen = "account is frozen"
}
data class Account(owner: String, balance: u64 = 0, frozen: bool = false) {
fun deposit(amount: u64) { balance += amount }
fun withdraw(amount: u64): u64 throws BankError {
if (frozen) throw BankError.Frozen
if (amount > balance) throw BankError.Insufficient(amount, balance)
balance -= amount
balance
}
override fun toString() = "$owner: $balance"
}
fun main() {
var account = Account("Ada")
account.deposit(100)
when (account.withdraw(250)) {
Ok(left) -> println("withdrew, $left left")
Err(e) -> println("failed: $e")
}
println("$account")
}
main.rs, generated
#[derive(Debug)]
pub(crate) enum BankError {
Insufficient { needed: u64, available: u64 },
Frozen,
}
impl ::std::fmt::Display for BankError {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
match self {
Self::Insufficient { needed, available } => write!(f, "need {needed}, have {available}"),
Self::Frozen => f.write_str("account is frozen"),
}
}
}
impl ::std::error::Error for BankError {}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct Account {
pub(crate) owner: String,
pub(crate) balance: u64,
pub(crate) frozen: bool,
}
impl Account {
#[allow(dead_code)]
pub(crate) fn new(owner: String, balance: u64, frozen: bool) -> Self {
Self {
owner,
balance,
frozen,
}
}
pub(crate) fn deposit(&mut self, amount: u64) {
self.balance += amount;
}
pub(crate) fn withdraw(&mut self, amount: u64) -> ::std::result::Result<u64, BankError> {
if self.frozen {
return Err(BankError::Frozen.into());
}
if amount > self.balance {
return Err(BankError::Insufficient { needed: amount, available: self.balance }.into());
}
self.balance -= amount;
Ok(self.balance)
}
}
impl ::std::fmt::Display for Account {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
write!(f, "{}: {}", self.owner, self.balance)
}
}
fn main() {
let mut account = Account::new(String::from("Ada"), 0, false);
account.deposit(100);
match account.withdraw(250) {
Ok(left) => {
println!("withdrew, {left} left");
}
Err(e) => {
println!("failed: {e}");
}
}
println!("{account}");
}

The program from the README and the Rust that oxie emit generates for it, produced by the current compiler each time the site is published.

Why

Rust is fast, safe and has a fantastic ecosystem, but its syntax is dense. Oxie keeps Rust’s semantics — ownership, borrowing, traits, zero-cost abstractions — and gives it the syntax Kotlin and TypeScript developers already know:

You write (Oxie) Oxie generates (Rust)
val x = 1 / var y = 2 let x = 1; / let mut y = 2;
fun area(): f64 = w * h fn area(&self) -> f64 { self.w * self.h }
data class Point(x: f64, y: f64) struct + constructor + #[derive(Debug, Clone, Copy, PartialEq)]
"Hello, $name! ${x + 1}" format!("Hello, {name}! {}", x + 1)
String?, null, a?.b ?: c Option<String>, None, map/and_then/unwrap_or
fun f(): T throws, throw e -> Result<T, AnyError>, return Err(e.into())
try { ... } catch (e) { ... } a labeled block with ? turned into break
error enum E { NotFound(p: String) = "no $p" } enum + Display + Error + From impls
when (x) { 1, 2 -> a; in 3..=9 -> b; else -> c } match
xs.filter { it > 2 }.sum_of { it.price } xs.iter().filter(..).cloned().collect(), iter().map(..).sum()
total(nums) with fun total(xs: &[i32]) total(&nums) (arguments are borrowed for you)
fun show<T>(x: T) = "<$x>" fn show<T>(x: T) -> String where T: Display
shared class Node(var next: Node?) Rc<RefCell<..>> handle with getters, setters and forwarding methods
interface Shape { fun area(): f64 } trait Shape { fn area(&self) -> f64; }
fun String.shout() = ... an extension trait, imported everywhere automatically
operator fun plus(o: V): V impl Add for V

Method receivers are inferred: a method that changes a field takes &mut self, one that only reads takes &self, a function that returns Self without touching self is static.

When the borrow checker objects, the error says what happened in Oxie terms (“name was moved into register() on line 6”) and offers fixes your editor can apply.

Install

Oxie needs Rust 1.82 or newer with Cargo (install them with rustup), since Oxie programs are compiled by rustc. Crates you use can need a newer Rust: the current tokio needs 1.85.

Then install the oxie command (compiler driver, formatter and language server), as a prebuilt binary or from source:

Terminal window
# macOS and Linux
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/oxie-lang/oxie/releases/latest/download/oxie-installer.sh | sh
Terminal window
# Windows
powershell -ExecutionPolicy Bypass -c "irm https://github.com/oxie-lang/oxie/releases/latest/download/oxie-installer.ps1 | iex"
Terminal window
# with cargo-binstall, which downloads the prebuilt binary
cargo binstall oxie
# or build it from source
cargo install oxie

Binaries for macOS, Linux and Windows are also on the releases page. Projects oxie creates get the oxie-build helper from crates.io. To work on Oxie itself, see working on Oxie.