Traits

Unlike many other languages with classes, Inko doesn't support class inheritance. Instead, code is shared between classes using "traits". Traits are essentially blueprints for classes, specifying what methods must be implemented and/or providing default implementations of methods a class may wish to override.

Let's say we want to convert different class instances into strings. We can do so using traits:

import std.stdio.STDOUT

trait ToString {
  fn to_string -> String
}

class Cat {
  let @name: String
}

impl ToString for Cat {
  fn to_string -> String {
    @name
  }
}

class async Main {
  fn async main {
    let garfield = Cat { @name = 'Garfield' }

    STDOUT.new.print(garfield.to_string)
  }
}

Running this program produces the output "Garfield".

Default methods

In this example, the to_string method in the ToString trait is a required method. This means that any class that implements ToString must provide an implementation of the to_string method.

Default trait methods are defined as follows:

import std.stdio.STDOUT

trait ToString {
  fn to_string -> String {
    '...'
  }
}

class Cat {
  let @name: String
}

impl ToString for Cat {
  fn to_string -> String {
    @name
  }
}

class async Main {
  fn async main {
    let garfield = Cat { @name = 'Garfield' }

    STDOUT.new.print(garfield.to_string)
  }
}

Here the default implementation of to_string is to return the string '...'. Our implementation of ToString for Cat still overrides it, so the output is still "Garfield". Because the method is a default method, we can use it as-is as follows:

import std.stdio.STDOUT

trait ToString {
  fn to_string -> String {
    '...'
  }
}

class Cat {
  let @name: String
}

impl ToString for Cat {

}

class async Main {
  fn async main {
    let garfield = Cat { @name = 'Garfield' }

    STDOUT.new.print(garfield.to_string)
  }
}

If we now run the program, the output is "...".

Required traits

Traits can specify other traits that must be implemented before the trait itself can be implemented:

trait ToString {
  fn to_string -> String
}

trait ToUpperString: ToString {
  fn to_upper_string -> String {
    to_string.to_upper
  }
}

Here the ToUpperString trait states that for a type to be able to implement ToUpperString, it must also implement ToString.

You can also specify multiple required traits:

trait A {}
trait B {}
trait C: A + B {}

Here the trait C requires both A and B to be implemented.

Conflicting trait methods

It's possible for different traits to define methods with the same name. If a type tries to implement such traits, a compile-time error is produced. Inko doesn't support renaming of trait methods as part of the implementation, so you'll need to find a way to resolve such conflicts yourself.