Effect System
Aria tracks side effects in function signatures. This tells the compiler — and the AI generating code — exactly what a function can and cannot do. Effect enforcement is implemented: the compiler warns when impure built-in functions are called from pure functions, and user-defined effects are enforced.
Declaring Effects
Effects are declared with with [...] after the return type:
// Pure function — no effects
fn calculateTotal(items: [Item]) -> f64 =
items.map(.price).sum()
// I/O and filesystem effects
fn readConfig(path: str) -> Config ! IoError with [Io, Fs] {
content := io.readFile(path)?
json.decode[Config](content)?
}
// Network effects
fn fetchData(url: str) -> [byte] ! HttpError with [Net] {
net.get(url)?.body
}
// Async effects (concurrency)
fn processAll(urls: [str]) -> [Result] with [Async] {
scope {
urls.map(fn(url) => spawn fetch(url))
.map(fn(t) => t.await())
}
}
// FFI effects (foreign function calls)
fn callCLib(data: [byte]) -> i32 with [Ffi] {
...
} Built-in Effects
| Effect | Covers |
|---|---|
Io | I/O operations (print, stdin/stdout) |
Fs | Filesystem access (read, write, delete) |
Net | Network operations (HTTP, TCP, DNS) |
Async | Concurrency primitives (spawn, channels) |
Ffi | Foreign function interface calls |
Why Effects Matter
For the Compiler
The compiler enforces effect obligations. A pure function cannot call a function with effects without declaring them:
// Compile error: calculateTotal is pure but calls readFile which has [Io, Fs]
fn calculateTotal() -> f64 {
data := io.readFile("prices.json")? // ERROR
...
}
// Fix: declare the effects
fn calculateTotal() -> f64 ! IoError with [Io, Fs] {
data := io.readFile("prices.json")? // OK
...
} For AI Code Generation
- Pure functions are safe to cache, parallelize, reorder, and memoize
- Effect declarations make dependencies explicit — the AI knows what context is needed
- No hidden I/O — you can never accidentally call a network function from a "pure" computation
For Testing
Pure functions are trivially testable — no mocking needed:
// This function is pure, so tests are simple
fn calculateDiscount(price: f64, rate: f64) -> f64 =
price * (1.0 - rate)
test calculateDiscount {
assert calculateDiscount(100.0, 0.1) == 90.0
assert calculateDiscount(50.0, 0.25) == 37.5
} Effect Propagation
Effects propagate through the call chain. If function A calls function B which has [Io], then A must also declare [Io]:
fn helper() -> str ! IoError with [Io, Fs] {
io.readFile("data.txt")?
}
// Must declare Io and Fs because it calls helper()
fn main() -> str ! IoError with [Io, Fs] {
helper()?
}