Oxie is a concise, familiar language that compiles to plain Rust
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")}#[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:
# macOS and Linuxcurl --proto '=https' --tlsv1.2 -LsSf https://github.com/oxie-lang/oxie/releases/latest/download/oxie-installer.sh | sh# Windowspowershell -ExecutionPolicy Bypass -c "irm https://github.com/oxie-lang/oxie/releases/latest/download/oxie-installer.ps1 | iex"# with cargo-binstall, which downloads the prebuilt binarycargo binstall oxie
# or build it from sourcecargo install oxieBinaries 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.