Skip to content

Define operators for your types

NativeBrowserNode3 min read

Give a custom type useful arithmetic, comparison, or conversion behavior.

At a glance

fun operator +(other: Vector2): Vector2 {
    return Vector2(this.x + other.x, this.y + other.y);
}
  • Declare operator methods to give your types arithmetic and comparison.
  • The number of parameters selects the unary or binary form.

Back to overview

Operator overloading

A class or struct can give +, -, *, /, %, &, |, ^, <<, >>, ==, unary -, !, ~, and both implicit and explicit casts their own meaning with fun operator +(...) and fun implicit(): T / fun explicit(): T.

class Vector2 {
    public x: int;
    public y: int;

    public constructor(x: int, y: int) {
        this.x = x;
        this.y = y;
    }

    fun operator +(other: Vector2): Vector2 {
        return Vector2(this.x + other.x, this.y + other.y);
    }

    fun operator -(other: Vector2): Vector2 {
        return Vector2(this.x - other.x, this.y - other.y);
    }

    // A one-parameter method takes the binary `-`; a zero-parameter method (same symbol) takes
    // unary `-`. Arity tells them apart.
    fun operator -(): Vector2 {
        return Vector2(-this.x, -this.y);
    }

    fun operator ==(other: Vector2): bool {
        return this.x == other.x && this.y == other.y;
    }
}

fun main(): void {
    let a = Vector2(1, 2);
    let b = Vector2(3, 4);
    let c = a + b; // Vector2(4, 6)
    let d = -a; // Vector2(-1, -2)
    let same = a == a; // true
    let diff = a != b; // true — `!=` reuses `operator ==`, negated
}

Overloadable operators

Operator Declare Parameters Notes
a + b fun operator +(other: R): T 1 Strings keep built-in concatenation
a - b fun operator -(other: R): T 1
-a fun operator -(): T 0 Same symbol as subtraction; arity decides
a * b fun operator *(other: R): T 1
a / b fun operator /(other: R): T 1
a % b fun operator %(other: R): T 1
a & b fun operator &(other: R): T 1
a | b fun operator |(other: R): T 1
a ^ b fun operator ^(other: R): T 1
a << b fun operator <<(other: R): T 1
a >> b fun operator >>(other: R): T 1
a == b fun operator ==(other: R): bool 1 Must return bool
a != b — — Uses operator ==, negated
!a fun operator !(): T 0
~a fun operator ~(): T 0
<, <=, >, >= fun compare(other: T): int 1 Implement Comparable<T> instead of an operator
+=, -=, *=, /=, %= — — Expand to a = a + b and so on, using the binary overload
(U)a fun explicit(): U 0 Explicit cast only
(U)a, or let x: U = a fun implicit(): U 0 Also usable as an explicit cast

Rules:

  • A tagged method's own parameter list fixes the operator's arity: one parameter is a binary overload (the right-hand operand), zero parameters is a unary overload. +/*///%/the bitwise operators/== are binary-only; !/~ are unary-only; - may be either.
  • != has no operator of its own — a registered operator == also powers it, negated.
  • <, <=, >, >= are not tagged individually. Implement Comparable<Self> (see Interfaces § Built-in Equatable and Comparable) instead; all four ordering operators dispatch to its single compare method.
  • A type may declare multiple binary overloads of the same symbol with different right-hand operand types. An exact type match wins over a compatible match; equally good matches are ambiguous. Duplicate parameter types are errors, even when the return types differ.
  • Unary operators remain unique per symbol, and casts remain unique per target type.
  • Equality overloads must return bool. == and != select the same overload; != negates it.
  • Operators dispatch on the left operand. Support both operand orders by declaring an overload on each type (including through extend); operands are never automatically reversed.
  • String concatenation keeps its built-in behavior whenever either operand of + is a string.

For example, a type can declare both fun operator +(other: Vector2): Vector2 and fun operator +(other: int): Vector2. The right operand determines which method is called.

User-defined casts

fun implicit(): T / fun explicit(): T on a no-parameter method defines a conversion from the declaring type to the method's return type:

class Meters {
    public value: float;

    public constructor(value: float) {
        this.value = value;
    }

    // Explicit only: `(float)meters`, never inferred.
    fun explicit(): float {
        return this.value;
    }
}

class Money {
    public cents: int;

    public constructor(cents: int) {
        this.cents = cents;
    }

    // Implicit: assignable anywhere an `int` is expected, no cast syntax needed.
    fun implicit(): int {
        return this.cents;
    }
}

fun main(): void {
    let m = Meters(2.5);
    let f = (float)m; // explicit cast required

    let money = Money(150);
    let cents: int = money; // implicit conversion at a typed `let` binding
}

An explicit (T)expr cast accepts either implicit or explicit — implicit conversions are always also spellable explicitly. Implicit conversions themselves are currently recognized at typed let x: T = expr; bindings; elsewhere, cast explicitly.