Structs & Traits

Aria uses structs for data and traits for behavior. There is no inheritance — composition and trait implementation are the tools for abstraction.

Structs

type Point {
    x: f64
    y: f64
}

type Config {
    host: str = "localhost"
    port: u16 = 8080
    timeout: dur = 30s
}

// Construction
p := Point{x: 1.0, y: 2.0}
cfg := Config{}                // all defaults
cfg2 := Config{port: 9090}     // partial override

// Immutable update
cfg3 := cfg2.{timeout: 60s, host: "0.0.0.0"}

Inherent Methods

Methods attached directly to a type (no trait needed):

impl Point {
    fn new(x: f64, y: f64) -> Point = Point{x, y}

    fn distance(self, other: Point) -> f64 {
        dx := self.x - other.x
        dy := self.y - other.y
        (dx * dx + dy * dy).sqrt()
    }

    fn origin() -> Point = Point{x: 0.0, y: 0.0}
}

p := Point.new(3.0, 4.0)
d := p.distance(Point.origin())

Traits

Traits define shared behavior. All implementations are explicit — no implicit interface satisfaction.

trait Display {
    fn display(self) -> str
}

trait Validate {
    fn validate(self) -> bool ! ValidationError
}

// Trait with default implementation
trait Describe {
    fn name(self) -> str
    fn describe(self) -> str = "I am a {self.name()}"
}

Implementing Traits

impl Display for Point {
    fn display(self) -> str = "({self.x}, {self.y})"
}

impl Display for Config {
    fn display(self) -> str =
        "{self.host}:{self.port} (timeout: {self.timeout})"
}

// Generic implementation
impl[T: Display] Display for Stack[T] {
    fn display(self) -> str {
        items := self.items
            .map(fn(x) => x.display())
            .join(", ")
        "Stack[{items}]"
    }
}

Supertraits

A trait can require another trait as a prerequisite:

trait Ordered: Eq {
    fn compare(self, other: Self) -> i64
}

// Any type implementing Ordered must also implement Eq

Associated Types

Traits can define associated types that implementations must specify:

trait Iterator {
    type Item
    fn next(mut self) -> Item?
}

impl Iterator for Counter {
    type Item = i64
    fn next(mut self) -> i64? {
        ...
    }
}

Dynamic Dispatch with dyn Trait

Use dyn Trait for runtime polymorphism via vtable dispatch:

fn printAll(items: [dyn Display]) {
    for item in items {
        println(item.display())
    }
}

Use generics when the type is known at compile time (zero overhead). Use dyn Trait for heterogeneous collections.

Derived Traits

Common traits can be automatically derived. The compiler generates actual method bodies — Eq generates field-by-field comparison, Clone generates field copying, Debug generates string formatting:

type User {
    id: i64
    name: str
    email: str
} derives [Eq, Clone, Debug, Json]

Struct equality with derives [Eq] generates == and != that compare each field.

Built-in Traits

TraitPurpose
Eq, OrdEquality and ordering
HashHashing (for map keys, sets)
Display, DebugString conversion
CloneExplicit copying
Convert[T], TryConvert[T]Type conversions
Iterable, Iterator[T]Iteration protocol
Send, ShareConcurrency bounds
DropCleanup on deallocation

The Drop Trait & Destructors

Types that need cleanup on deallocation implement Drop. Destructors run in LIFO order per scope:

impl Drop for Connection {
    fn drop(mut self) {
        self.close()
    }
}

// Connection.drop() called automatically when conn goes out of scope

RAII with with Blocks

The with block provides scoped resource management — the resource is automatically cleaned up when the block exits:

with conn := db.connect("postgres://localhost/mydb")? {
    conn.query("SELECT * FROM users")?
    // conn is automatically closed when this block exits
}

Marker Traits: Send & Share

Send and Share are marker traits for concurrency safety. They are automatically derived for primitive types and types composed of primitives:

  • Send — the type can be moved to another task
  • Share — the type can be accessed from multiple tasks simultaneously

Sum Types with Methods

type Shape =
    | Circle { radius: f64 }
    | Rect { w: f64, h: f64 }
    | Point

impl Shape {
    fn area(self) -> f64 = match self {
        Circle{r} => 3.14159 * r * r
        Rect{w, h} => w * h
        Point => 0.0
    }

    fn isSmall(self) -> bool = self.area() < 10.0
}

Next Steps