Skip to content

Protocols

Protocols define a set of functions that a type must implement. They enable generic programming through bounded type parameters, with all dispatch resolved at compile time (no dynamic dispatch).

A protocol declares required method signatures using Self as a placeholder for the implementing type:

protocol Serializable {
fn serialize(a: Self) -> String
fn deserialize(raw: String) -> Result[Self, String]
}

Protocols can include effect fn methods:

protocol Storage {
effect fn save(a: Self, path: String) -> Result[Unit, String]
effect fn load(path: String) -> Result[Self, String]
}

A type declares protocol satisfaction with : ProtocolName in its type declaration. Methods are defined as convention functions with the type name prefix:

type Config: Serializable = {
key: String,
value: String,
}
fn Config.serialize(c: Config) -> String =
c.key + "=" + c.value
fn Config.deserialize(raw: String) -> Result[Config, String] = {
let parts = string.split(raw, "=")
match (list.get(parts, 0), list.get(parts, 1)) {
(some(k), some(v)) => ok({ key: k, value: v }),
_ => err("invalid format"),
}
}

Methods use UFCS, so both call styles work:

let s = Config.serialize(config)
let s = config.serialize() // equivalent via UFCS

Protocols constrain generic type parameters:

fn show[T: Serializable](item: T) -> String =
item.serialize()
fn save_all[T: Serializable](items: List[T], path: String) -> String = {
let lines = list.map(items, (item) => item.serialize())
string.join(lines, "\n")
}

Only types that satisfy the protocol can be used as arguments:

show(config) // OK: Config implements Serializable
show(42) // Compile error: Int does not implement Serializable

Several protocols are built into the language:

ProtocolDescriptionDeriving
EqEquality comparison (==, !=)Automatic for all value types
HashHash computationAutomatic for all value types
ReprString representationBuilt-in convention
OrdOrdering (<, <=, >, >=)Built-in convention
CodecEncode/decodeBuilt-in convention

Eq and Hash are compiler-derived from the type structure. No deriving is needed:

type Color = Red | Green | Blue
let same = Red == Red // true, just works
let diff = Red != Blue // true, just works

For more complex type-class patterns, Almide supports protocol and impl blocks:

protocol Showable {
fn show(a: Self) -> String
}
impl Showable for Point {
fn show(a: Point) -> String =
"(${float.to_string(a.x)}, ${float.to_string(a.y)})"
}
  • No dynamic dispatch — all protocol-bounded generics are monomorphized at compile time
  • No implicit instance resolution — types explicitly declare protocol satisfaction
  • No operator overloading — built-in operators have fixed semantics
  • No inheritance — use composition and protocols instead