Static copy
Copied from docs/en/manual/interfaces-and-contracts.md in https://github.com/poppy-team/aipo-lang (MIT). Pinned to revision ce47c4e070404fc41722ccd8e2b7b808b99062a8, blob 1d9fd952c68167fbdac030b945cd13d80a1fa5c5. The source repository remains canonical; this copy is refreshed through a sync pull request, not live.
Aipo bridges dynamic flexibility with signature contracts, structural invariants, and automatic structural interface subtyping.
struct) Structures define aggregate types delimited by curly braces { ... }.
By safe and predictable design default, all fields in a struct are immutable. When a field must be mutable during the instance lifecycle, declare it explicitly with the var keyword:
struct Server {
id
created_at
var status = "offline"
var cpu_load = 0.0
}
# Structural instantiation using symmetric key-value ':'
let s = Server{
id: "srv-1",
created_at: 1600000000,
status: "online",
cpu_load: 0.42,
}
io.println(s.id) # "srv-1"
io.println(s.status) # "online"Reassigning an immutable field after construction triggers the static semantic diagnostic AIPO_SEM_IMMUTABLE_FIELD_REASSIGN.
init) The init hook is bound to a type using the Type: prefix to validate, normalize, and initialize instance fields before publication:
struct User {
email: String
name: String
}
User:init(email: String, name: String) {
if not email.contains("@") {
return fail("Invalid email address format")
}
self.email = email
self.name = name
}
let u = User{ email: "user@example.com", name: "Dev" }
io.println(u.email) # "user@example.com"invariant) Invariants declare logical predicates bound to the type that must remain true throughout the lifetime of the object:
struct Interval {
var start: Int = 0
var end_val: Int = 0
}
Interval:init(start: Int, end_val: Int) {
self.start = start
self.end_val = end_val
}
Interval:invariant {
self.start <= self.end_val
}
let inter = Interval{ start: 5, end_val: 10 }
io.println(inter.start) # 5
io.println(inter.end_val) # 10Whenever a field is mutated, the invariant predicate is automatically re-evaluated. If it fails, the operation is rejected. Inside an attempt { ... } block, provisional mutations are automatically rolled back by the transaction journal.
var self) Methods are declared using the Type:method_name syntax. The receiver self is implicit in read-only methods and explicit as var self when mutating.
By default, the self receiver is read-only. When a method needs to mutate internal instance state, it explicitly declares var self, harmonizing method mutability with the universal variable rules of the language:
struct Counter {
var count: Int = 0
}
# Read-only method: self is implicit and immutable (no 'fn' keyword)
Counter:current() -> Int {
return self.count
}
# Mutator method: var self explicitly signals state modification
Counter:increment(var self) {
self.count += 1
}interface) Interfaces define method signatures without bodies. In Aipo, interface conformance requires no ceremony: subtyping is structural and automatic (inspired by Go and Luau).
If a struct or enum implements all methods required by an interface with compatible signatures and contracts, it automatically satisfies the interface:
interface Drawable {
draw() -> String
}
#!satisfies Drawable
struct Button {
label: String
}
Button:draw() -> String {
return f"[Button: {self.label}]"
}
# Accepts any type satisfying the Drawable contract
fn render_element(item: Drawable) -> String {
return item.draw()
}
let btn = Button{ label: "Submit" }
io.println(render_element(btn)) # "[Button: Submit]"::) Free functions can be batch-promoted to methods of a type using the :: token:
fn perimeter(self) -> Float {
return 2.0 * (self.width + self.height)
}
fn scale(var self, factor: Float) {
self.width *= factor
self.height *= factor
}
Rectangle::[perimeter, scale]Rule: The promoted function MUST declare self (or var self) as its first parameter.
enum) enum defines closed algebraic sum types with compiler-verified exhaustiveness in match:
enum ConnectionState {
Disconnected,
Connecting(attempt: Int),
Connected { ip: String, ping_ms: Int },
Error(reason: String),
}
fn describe(state: ConnectionState) -> String {
return match state {
when ConnectionState.Disconnected { "Offline" }
when ConnectionState.Connecting(a) { f"Attempt #{a}" }
when ConnectionState.Connected { ip, ping_ms } { f"{ip}:{ping_ms}" }
when ConnectionState.Error(r) { f"Error: {r}" }
}
}#!name) Directives are compiler annotations attached to the immediately following item:
#!satisfies I1, I2: Enforces interface conformance at compile time.#!test / #!test[tag] / #!test("name"): Marks unit tests.#!deprecated("msg"): Warns on usage of obsolete APIs.#!todo("msg"): Tracks technical debt.