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

TypeDescriptionSize
i8, i16, i32, i64Signed integers1, 2, 4, 8 bytes
u8, u16, u32, u64Unsigned integers1, 2, 4, 8 bytes
f32, f64Floating point4, 8 bytes
boolBoolean1 byte
strUTF-8 immutable stringVaries
charUnicode character4 bytes
byteAlias for u81 byte
durDuration (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

Next Steps