Skip to content

Impl Blocks

impl blocks let you attach behavior directly to a type.

They are how Calibre gives a type methods and associated items.

This says that get_language belongs to CountryBase.

type CountryBase := struct { language : Language };
impl CountryBase {
const get_language := fn (self : &CountryBase) -> Language => self.language;
};
let country := CountryBase { language : Language.SPANISH };
print(country.get_language());

The first parameter usually acts like the receiver.

Common receiver styles are:

  • self : &Type for shared access
  • self : &mut Type for mutable access
  • self : Type when working with the value directly

where Type can either be Self or the name of the type. By doing so you allow for the function to be treated as a static function.

This works for built-in types as well as user-defined ones. For example:

impl int {
const days := fn (self : Self) -> Self => self * 24 * 60 * 60;
};
print(5.days());

An impl block can also contain associated functions and constants, not just instance methods.

impl CountryBase {
const default_language := Language.ENGLISH : 1;
const english := fn -> CountryBase => CountryBase { language : Self.default_language };
};

Associated items are accessed through the type itself.

let country := CountryBase.english();

Inside an impl block, Self refers to the type currently being implemented.

impl CountryBase {
const clone_language := fn (self : &Self) -> Language => self.language;
};

Calibre also uses impl together with traits:

impl Person for User {
const name := fn (self : &User) -> str => self.name;
};

That trait-specific form is covered in the next section on traits.

Use impl TypeName { ... } when you want methods or associated items that belong directly to a type, and impl TraitName for TypeName { ... } when you are implementing a trait.