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
| Trait | Purpose |
|---|---|
Eq, Ord | Equality and ordering |
Hash | Hashing (for map keys, sets) |
Display, Debug | String conversion |
Clone | Explicit copying |
Convert[T], TryConvert[T] | Type conversions |
Iterable, Iterator[T] | Iteration protocol |
Send, Share | Concurrency bounds |
Drop | Cleanup 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 taskShare— 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
}