Skip to content

Overloads

Calibre supports operator overloading through @overload.

This lets a type customize how certain basic expressions behave.

You declare overloads directly on the type definition.

type Vec2 := struct { x y : int } @overload {
const "+" := fn (a : Vec2, b : Vec2) -> Vec2 => Vec2 { x : a.x + b.x, y : a.y + b.y };
};

In this example, v + w now uses the custom + implementation for Vec2.

let v := Vec2 { x : 1, y : 2 };
let w := Vec2 { x : 3, y : 4 };
print(v + w);

Overloads are written as specially named const items inside the @overload block.

Some of the core operations that can be overloaded include:

  • arithmetic operators like "+"
  • equality with "="
  • indexing with "[]"
  • indexed assignment with "[]="
  • membership with "in"
  • conversions with "as"

For example, equality can be customized:

type Vec2 := struct { x y : int } @overload {
const "=" := fn (a b : Vec2) -> bool => a.x = b.x && a.y = b.y;
};

Now expressions like v = other_vec use this logic.

Indexing can also be overloaded.

type Vec2 := struct { x y : int } @overload {
const "[]" := fn (self : Vec2, idx : int) -> int => {
if idx = 0 => return self.x;
if idx = 1 => return self.y;
0;
};
};

That allows expressions like:

print(v[0]);
print(v[1]);

Indexed assignment can be overloaded separately with "[]=".

type Vec2 := struct { x y : int } @overload {
const "[]=" := fn (self : Vec2, idx : int, value : int) -> Vec2 => {
if idx = 0 => self.x := value;
if idx = 1 => self.y := value;
self;
};
};

This enables syntax like:

let mut v := Vec2 { x : 1, y : 2 };
v[0] := 9;

The in operator can also be customized.

type Vec2 := struct { x y : int } @overload {
const "in" := fn (value : int, vec : Vec2) -> bool => value = vec.x || value = vec.y;
};

So code like 2 in v can mean whatever membership rule makes sense for the type.

The as operation can be overloaded too.

type Vec2 := struct { x y : int } @overload {
const "as" := fn (self : Vec2) -> str => "Vec2";
};

This makes expressions like v as str use your custom conversion behavior.

Overloads are not just for user-defined structs. They can also be applied to other types.

type int := int @overload {
const "as" := fn (self : int) -> str => "int";
};

Use @overload when you want a type to participate naturally in Calibre expressions like +, [], in, or as, but keep in mind that overloaded behavior should still feel intuitive to someone reading the code.