The Effects System
Kira tracks effects in types. This is one of its most important features.
Pure Functions (Default)
By default, functions are pure. Pure functions:
- Always return the same output for the same input
- Have no side effects (no I/O, no mutation)
- Can be freely memoized, parallelized, or reordered
fn add(a: i32, b: i32) -> i32 {
return a + b
}
fn fibonacci(n: i32) -> i32 {
if n <= 1 {
return n
}
return fibonacci(n - 1) + fibonacci(n - 2)
}Effect Functions
Effect functions are marked with the effect keyword. They can perform side effects like I/O:
effect fn greet(name: string) -> void {
std.io.println("Hello, " + name + "!")
}
effect fn read_input() -> IO[string] {
return std.io.read_line()
}Effect Rules
- Pure functions cannot call effect functions
- Effect functions can call both pure and effect functions
- The main function is always an effect function
// This is a compile error:
fn bad_function() -> i32 {
std.io.println("Hello") // ERROR: pure function cannot perform I/O
return 42
}
// This is correct:
effect fn good_function() -> void {
let x: i32 = add(1, 2) // OK: calling pure function
std.io.println("Sum: " + to_string(x)) // OK: we're an effect function
}The Main Function
The entry point is always an effect function:
effect fn main() -> void {
let result: i32 = factorial(5)
std.io.println("5! = " + to_string(result))
}Common Effect Patterns
// File I/O
effect fn read_config(path: string) -> Result[Config, string] {
let content: string = std.fs.read_file(path)?
return Ok(parse_config(content))
}
// User interaction
effect fn get_user_input(prompt: string) -> string {
std.io.print(prompt)
match std.io.read_line() {
Ok(line) => { return line }
Err(_) => { return "" }
}
}
// Timed operations
effect fn benchmark(f: fn() -> void) -> i64 {
let start: i64 = std.time.now()
f()
let end: i64 = std.time.now()
return end - start
}