Syntax Comparison
This page provides side-by-side syntax comparisons between Oxide, Rust, and Kotlin. Oxide aims to provide a familiar syntax for developers coming from Kotlin and Swift while transpiling to standard Rust.
Variable Declarations
| Feature | Oxide | Rust | Kotlin |
|---|---|---|---|
| Immutable | let x = 10 | let x = 10; | val x = 10 |
| Mutable | var x = 10 | let mut x = 10; | var x = 10 |
| Type annotation | let x: Int = 10 | let x: isize = 10; | val x: Int = 10 |
| Constant | const MAX: Int = 100 | const MAX: isize = 100; | const val MAX = 100 |
Code Examples
// Oxide
let name = "Alice"
var count = 0
count++
// Rust
let name = "Alice";
let mut count = 0;
count += 1;
// Kotlin
val name = "Alice"
var count = 0
count++
Null Safety
| Feature | Oxide | Rust | Kotlin |
|---|---|---|---|
| Nullable type | T? | Option<T> | T? |
| Null literal | null | None | null |
| Safe call | x?.method() | x.as_ref().map(|v| v.method()) | x?.method() |
| Null coalesce | x ?? default | x.unwrap_or(default) | x ?: default |
| Force unwrap | x!! | x.unwrap() | x!! |
| Null check | if x != null { } | if let Some(x) = x { } | if (x != null) { } |
Code Examples
// Oxide
let name: String? = fetchName()
let length = name?.length ?? 0
let forced = name!!
// Rust
let name: Option<String> = fetch_name();
let length = name.as_ref().map(|n| n.len()).unwrap_or(0);
let forced = name.unwrap();
// Kotlin
val name: String? = fetchName()
val length = name?.length ?: 0
val forced = name!!
Functions
| Feature | Oxide | Rust | Kotlin |
|---|---|---|---|
| Basic function | fn add(a: Int, b: Int): Int { } | fn add(a: isize, b: isize) -> isize { } | fun add(a: Int, b: Int): Int { } |
| Expression body | fn add(a: Int, b: Int): Int { a + b } | fn add(a: isize, b: isize) -> isize { a + b } | fun add(a: Int, b: Int) = a + b |
| Default params | fn greet(name: String = "World") | N/A (use Option) | fun greet(name: String = "World") |
| Async function | async fn fetch(): String | async fn fetch() -> String | suspend fun fetch(): String |
Code Examples
// Oxide
fn greet(name: String = "World"): String {
return "Hello, $name!"
}
async fn fetchUser(id: Int): User {
let data = await api.get(id)
return User.from(data)
}
// Rust
fn greet(name: Option<&str>) -> String {
let name = name.unwrap_or("World");
format!("Hello, {}!", name)
}
async fn fetch_user(id: isize) -> User {
let data = api.get(id).await;
User::from(data)
}
// Kotlin
fun greet(name: String = "World"): String {
return "Hello, $name!"
}
suspend fun fetchUser(id: Int): User {
val data = api.get(id)
return User.from(data)
}
Closures
| Feature | Oxide | Rust | Kotlin |
|---|---|---|---|
| Basic closure | { x -> x * 2 } | |x| x * 2 | { x -> x * 2 } |
| Implicit param | { it * 2 } | N/A | { it * 2 } |
| Trailing lambda | list.map { it * 2 } | list.iter().map(|x| x * 2) | list.map { it * 2 } |
| Capture by value | capture { x -> use(x) } | move |x| use(x) | N/A (automatic) |
Code Examples
// Oxide
let numbers = [1, 2, 3, 4, 5]
let doubled = numbers.iter().map { it * 2 }.toArray()
let sum = numbers.iter().fold(0) { acc, x -> acc + x }
// Rust
let numbers = vec![1, 2, 3, 4, 5];
let doubled: Vec<_> = numbers.iter().map(|x| x * 2).collect();
let sum = numbers.iter().fold(0, |acc, x| acc + x);
// Kotlin
val numbers = listOf(1, 2, 3, 4, 5)
val doubled = numbers.map { it * 2 }
val sum = numbers.fold(0) { acc, x -> acc + x }
Pattern Matching
| Feature | Oxide | Rust | Kotlin |
|---|---|---|---|
| Match expression | match value { } | match value { } | when (value) { } |
| Match arm | 1 -> "one" | 1 => "one", | 1 -> "one" |
| Default case | else -> "other" | _ => "other", | else -> "other" |
| Null pattern | null -> "none" | None => "none", | null -> "none" |
| Type check | if x is String { } | if let Some(s) = x.downcast_ref::<String>() { } | if (x is String) { } |
| Pattern binding | if x is Some(value) { } | if let Some(value) = x { } | if (x is Some) { val value = x.value } |
Code Examples
// Oxide
let result = match status {
Status.active -> "Running"
Status.error(msg) -> "Error: $msg"
null -> "Unknown"
else -> "Other"
}
if user is Admin(level) {
println!("Admin level: $level")
}
// Rust
let result = match status {
Status::Active => "Running",
Status::Error(msg) => format!("Error: {}", msg),
None => "Unknown",
_ => "Other",
};
if let Some(Admin { level }) = user {
println!("Admin level: {}", level);
}
// Kotlin
val result = when (status) {
Status.ACTIVE -> "Running"
is Status.Error -> "Error: ${status.msg}"
null -> "Unknown"
else -> "Other"
}
if (user is Admin) {
println!("Admin level: ${user.level}")
}
Async/Await
| Feature | Oxide | Rust | Kotlin |
|---|---|---|---|
| Async function | async fn fetch() | async fn fetch() | suspend fun fetch() |
| Await | await expr | expr.await | Direct call |
| Async block | async { ... } | async { ... } | async { ... } |
| Task spawn | Task { ... } | tokio::spawn(async { ... }) | launch { ... } |
| Concurrent | async let a = f1() | tokio::join!(f1(), f2()) | async { f1() } |
| Timeout | within(5.seconds) { } | tokio::time::timeout(Duration::from_secs(5), ...) | withTimeout(5000) { } |
Code Examples
// Oxide
async fn fetchAll(): [User] {
async let users = fetchUsers()
async let posts = fetchPosts()
let (u, p) = await (users, posts)
return within(5.seconds) {
await processData(u, p)
}
}
// Rust
async fn fetch_all() -> Vec<User> {
let (users, posts) = tokio::join!(
fetch_users(),
fetch_posts()
);
tokio::time::timeout(
Duration::from_secs(5),
process_data(users, posts)
).await;
}
// Kotlin
suspend fun fetchAll(): List<User> {
val users = async { fetchUsers() }
val posts = async { fetchPosts() }
val (u, p) = users.await() to posts.await()
return withTimeout(5000) {
processData(u, p)
}
}
Generics
| Feature | Oxide | Rust | Kotlin |
|---|---|---|---|
| Generic function | fn <T> identity(x: T): T | fn identity<T>(x: T) -> T | fun <T> identity(x: T): T |
| Generic struct | struct Box<T>(value: T) | struct Box<T> { value: T } | class Box<T>(val value: T) |
| Type bound | fn <T: Clone> dup(x: T) | fn dup<T: Clone>(x: T) | fun <T: Cloneable> dup(x: T) |
| Where clause | where T: Display | where T: Display | where T: Display |
| Opaque type | fn get(): some Iterator | fn get() -> impl Iterator | N/A |
Code Examples
// Oxide
fn <T: Clone> duplicate(item: T): (T, T) {
return (item.clone(), item)
}
struct Container<T>(value: T)
extension <T> Container<T> {
fn get(): &T = &self.value
}
// Rust
fn duplicate<T: Clone>(item: T) -> (T, T) {
(item.clone(), item)
}
struct Container<T> {
value: T,
}
impl<T> Container<T> {
fn get(&self) -> &T {
&self.value
}
}
// Kotlin
fun <T: Cloneable> duplicate(item: T): Pair<T, T> {
return item.clone() to item
}
class Container<T>(val value: T) {
fun get(): T = value
}
Traits and Extensions
| Feature | Oxide | Rust | Kotlin |
|---|---|---|---|
| Trait definition | trait Drawable { } | trait Drawable { } | interface Drawable { } |
| Trait impl | extension Point: Drawable { } | impl Drawable for Point { } | class Point : Drawable { } |
| Inherent impl | extension Point { } | impl Point { } | (methods in class) |
| Extension function | extension String { fn double() } | N/A (use trait) | fun String.double() |
Code Examples
// Oxide
trait Drawable {
fn draw()
fn area(): Float64
}
extension Circle: Drawable {
fn draw() {
println!("Drawing circle")
}
fn area(): Float64 = 3.14159 * self.radius * self.radius
}
// Rust
trait Drawable {
fn draw(&self);
fn area(&self) -> f64;
}
impl Drawable for Circle {
fn draw(&self) {
println!("Drawing circle");
}
fn area(&self) -> f64 {
3.14159 * self.radius * self.radius
}
}
impl Circle {
fn new(radius: f64) -> Circle {
Circle { radius }
}
}
// Kotlin
interface Drawable {
fun draw()
fun area(): Double
}
class Circle(val radius: Double) : Drawable {
override fun draw() {
println!("Drawing circle")
}
override fun area() = 3.14159 * radius * radius
companion object {
fun new(radius: Double) = Circle(radius)
}
}
Visibility Modifiers
| Feature | Oxide | Rust | Kotlin |
|---|---|---|---|
| Public | public fn foo() | pub fn foo() | public fun foo() (default) |
| Private | private fn foo() or default | fn foo() (default) | private fun foo() |
| Crate-wide | internal fn foo() | pub(crate) fn foo() | internal fun foo() |
| Module parent | protected fn foo() | pub(super) fn foo() | protected fun foo() |
Error Handling
| Feature | Oxide | Rust | Kotlin |
|---|---|---|---|
| Throwing function | fn parse() throws: Int | fn parse() -> Result<isize, Error> | fun parse(): Int (throws) |
| Try expression | try parse() | parse()? | try { parse() } |
| Try or null | try? parse() | parse().ok() | runCatching { parse() }.getOrNull() |
| Do-catch | do { } catch e { } | match result { Ok(v) => ..., Err(e) => ... } | try { } catch (e: Exception) { } |
Code Examples
// Oxide
fn parseInt(s: String) throws: Int {
if s.isEmpty() {
throw ParseError("Empty string")
}
return s.parse().unwrap()
}
fn main() {
do {
let n = try parseInt("42")
println!("Parsed: $n")
} catch e {
println!("Error: $e")
}
// Or use try? for optional result
let maybe = try? parseInt("invalid")
}
// Rust
fn parse_int(s: &str) -> Result<i32, ParseError> {
if s.is_empty() {
return Err(ParseError::new("Empty string"));
}
s.parse().map_err(|_| ParseError::new("Invalid"))
}
fn main() {
match parse_int("42") {
Ok(n) => println!("Parsed: {}", n),
Err(e) => println!("Error: {}", e),
}
// Or use .ok() for optional result
let maybe = parse_int("invalid").ok();
}
String Interpolation
| Feature | Oxide | Rust | Kotlin |
|---|---|---|---|
| Simple | "Hello, $name" | format!("Hello, {}", name) | "Hello, $name" |
| Expression | "Sum: ${a + b}" | format!("Sum: {}", a + b) | "Sum: ${a + b}" |
| Debug format | "Debug: #value" | format!("Debug: {:?}", value) | N/A |
| Multi-line | $$"""...""" | r#"..."# | """...""" |
Code Examples
// Oxide
let name = "World"
let greeting = "Hello, $name!"
let math = "Result: ${2 + 2}"
let debug = "Value: #someStruct"
// Rust
let name = "World";
let greeting = format!("Hello, {}!", name);
let math = format!("Result: {}", 2 + 2);
let debug = format!("Value: {:?}", some_struct);
// Kotlin
val name = "World"
val greeting = "Hello, $name!"
val math = "Result: ${2 + 2}"