Docs/Types

Interfaces

Accept different types through one set of methods.

On this page

An interface lists methods that different classes provide. A function that takes the interface accepts any of those classes.

Declare and implement#

Valk
interface Shape {
    fn area() float;
    get name: String;
}

class Square is Shape {
    side: float
    fn area() float { return this.side * this.side }
    get name: String { return "square" }
}

class Circle is Shape {
    radius: float
    fn area() float { return 3.14 * this.radius * this.radius }
    get name: String { return "circle" }
}

fn report(shape: Shape) {
    println(shape.name + ": " + shape.area())
}

fn main() {
    report(Square { side: 2.0 })   // square: 4
    report(Circle { radius: 1.0 }) // circle: 3.14
}
  • Interface methods end in ;. Getters are declared with get name: Type;.
  • A class lists its interfaces after is, separated by commas: class User is Named, Printable.
  • Each method must match the declared arguments, return type and error type.
  • Only classes implement interfaces, not structs.

Default methods#

An interface method with a body is used by classes that do not define their own:

Valk
interface Greeter {
    get name: String;
    fn greet() String {
        return "Hello, " + this.name
    }
}

class Person is Greeter {
    first: String
    get name: String { return this.first }
}

class Robot is Greeter {
    get name: String { return "R2" }
    fn greet() String { return "Beep" }
}

fn main() {
    let list = Array[Greeter]{ Person { first: "Ada" }, Robot {} }
    each list as item : println(item.greet())
    // Hello, Ada
    // Beep
}

Errors in interface methods#

An interface method can declare an error. A class method that never fails may still implement it; callers through the interface handle the error, direct callers do not:

Valk
error LoadError (missing)

interface Source {
    fn load() String !LoadError;
}

class Fixed is Source {
    text: String
    fn load() String { return this.text }
}

fn main() {
    let fixed = Fixed { text: "data" }
    println(fixed.load())        // no handler needed
    let source: Source = fixed
    println(source.load() !? "") // data
}

Check the concrete class#

is_a tests which class an interface value holds. match also gives the object as that class. A default case is required, because other classes may implement the interface:

Valk
interface Animal {
    fn sound() String;
}

class Dog is Animal {
    fn sound() String { return "woof" }
    fn fetch() String { return "fetching" }
}

class Cat is Animal {
    fn sound() String { return "meow" }
}

fn main() {
    let pet: Animal = Dog {}
    if pet is_a Dog : println("a dog")

    match pet {
        Dog as dog => println(dog.fetch())
        default => println(pet.sound())
    }
}

Interface or generics#

Use an interface when one list or parameter must accept different classes. Use generics when the caller's concrete type should be kept, and a tagged union when the set of types is closed and known.