Variables & Types
Aria has a strong, static type system with full inference. Every value has a known type at compile time, and there are no implicit conversions.
Variable Bindings
// Immutable (default)
x := 42
name := "aria"
// Mutable
mut count := 0
count = count + 1
// Explicit type annotation
port: u16 = 8080
// Compile-time constant
const PI = 3.14159
const MAX_SIZE = 1024 Immutable bindings cannot be reassigned. Use mut only when you need to change a value.
Primitive Types
| Type | Description | Size |
|---|---|---|
i8, i16, i32, i64 | Signed integers | 1, 2, 4, 8 bytes |
u8, u16, u32, u64 | Unsigned integers | 1, 2, 4, 8 bytes |
f32, f64 | Floating point | 4, 8 bytes |
bool | Boolean | 1 byte |
str | UTF-8 immutable string | Varies |
char | Unicode character | 4 bytes |
byte | Alias for u8 | 1 byte |
dur | Duration (planned) | 8 bytes |
There is no platform-dependent int type. You always choose a specific width.
Numeric Literals
Aria supports multiple numeric bases with optional underscore separators for readability:
decimal := 1_000_000
hex := 0xFF
octal := 0o77
binary := 0b1010_0101 Strings
Strings are UTF-8 encoded and immutable, with small string optimization (≤23 bytes stored inline).
s := "hello, world"
s.len() // byte length (O(1))
s.charCount() // codepoint count (O(n))
s[2..5] // substring (zero-copy)
s.split(",") // split into list
s.trim() // strip whitespace
s.contains("world") // search
s.replace("o", "0") // replace
s.toUpper() // case conversion
// String interpolation
name := "Aria"
println("Hello, {name}! 2+2 = {2 + 2}") Collections
Lists
nums := [1, 2, 3, 4, 5]
nums.len() // 5
nums[0] // 1
nums.map(fn(x) => x * 2) // [2, 4, 6, 8, 10]
nums.filter(fn(x) => x > 3) // [4, 5]
nums.fold(0, fn(a, b) => a + b) // 15
nums.sort()
// List comprehension
squares := [x * x for x in 1..=10] Maps
ages := Map{"alice": 30, "bob": 25}
ages["alice"] // 30
ages.contains("eve") // false
ages.keys() // ["alice", "bob"]
for (k, v) in ages {
println("{k}: {v}")
} Sets
s := Set{1, 2, 3}
s.contains(2) // true
s.add(4)
s.remove(1) Structs
type Point {
x: f64
y: f64
}
type Config {
host: str = "localhost" // default value
port: u16 = 8080 // default value
debug: bool = false
}
p := Point{x: 1.0, y: 2.0}
cfg := Config{} // uses all defaults
cfg2 := Config{port: 9090} // override one field
// Immutable update (copy with changes)
cfg3 := cfg2.{debug: true} Sum Types (Tagged Unions)
type Shape =
| Circle { radius: f64 }
| Rect { w: f64, h: f64 }
| Point
type Option[T] = Some(T) | None
type Result[T, E] = Ok(T) | Err(E) Sum types must be handled exhaustively — the compiler rejects incomplete match expressions.
Type Aliases
Create an alias for an existing type with alias:
// Alias — interchangeable with the underlying type
alias UserId = i64
alias Handler = fn(Request) -> Response
// Newtype — distinct type, not interchangeable
type Timestamp = i64 derives [Eq, Ord] Dynamic Dispatch with dyn Trait
When you need runtime polymorphism, use dyn Trait for trait objects with vtable dispatch:
fn printAll(items: [dyn Display]) {
for item in items {
println(item.display())
}
}
// Can pass mixed concrete types
printAll([circle, rect, point]) Use generics ([T: Display]) when the concrete type is known at compile time (zero overhead). Use dyn Trait when you need heterogeneous collections or runtime dispatch.
Type Conversions
Aria has three explicit conversion mechanisms. There are no implicit conversions.
Lossless: T(x)
a: i32 = 42
b: i64 = i64(a) // always safe
c: i32 = i32(b) // compile error — may lose data Checked: .to[T]()
big: i64 = 9999999999
small := big.to[i32]()? // Returns Result[i32, ConversionError]
small := big.to[i32]()! // Panics if it doesn't fit Truncating: .trunc[T]()
f: f64 = 3.7
n := f.trunc[i64]() // 3 (explicit data loss) Duration Literals
timeout := 30s
interval := 5m
long := 2h
short := 100ms