Core Interfaces

A few interfaces are shared by the whole standard library. They are ordinary interfaces declared in packages — the compiler gives none of them special treatment — and a type opts into each one with an extend block like any other.

InterfacePackageRequirementUsed by
EquatableCorefunc Equals(other: &Self) -> bool;searching, deduplication, hash keys
ComparableCorefunc Compare(other: &Self) -> Ordering;sorting, binary search, extremes
HashableCorefunc Hash() -> uint64;hash tables
IteratorCorenonenames the iterator role
IterableCorenonenames the container role
DisplayTextfunc WriteDisplay(writer: &var TextWriter, spec: FormatSpec) -> ! FormatError;{} placeholders, PrintLine
DebugTextfunc WriteDebug(writer: &var TextWriter, spec: FormatSpec) -> ! FormatError;inspection output

Each is imported by name — import Core::{ Comparable, Equatable, Ordering };, import Text::{ Display, FormatError, FormatSpec, TextWriter }; — and the package is listed under [Dependencies] in Rux.toml.

Equatable

pub interface Equatable {
    func Equals(other: &Self) -> bool;
}

Equals says whether two values of one type stand for the same thing. Because the operand is &Self, a value is only ever compared with another of its own type. An implementation must be an equivalence:

LawMeaning
Reflexivea.Equals(a) is true
Symmetrica.Equals(b) is b.Equals(a)
Transitivea.Equals(b) and b.Equals(c) imply a.Equals(c)

A type that cannot promise all three — one with a value that does not equal itself, as a floating-point NaN does not — should not implement Equatable. Generic code that relies on it is entitled to assume the laws hold.

Equals is a method, not an operator: implementing Equatable leaves == as it was, which for a structure is structural equality. To give == the same meaning, declare == as well.

Comparable and Ordering

pub interface Comparable {
    func Compare(other: &Self) -> Ordering;
}

Compare answers which of two values comes first with a three-way Ordering, so one call settles "before, same or after". An implementation must be a total order: for any pair exactly one of Less, Equal and Greater holds; swapping the operands swaps Less and Greater; and the relation is transitive. A type implementing both Comparable and Equatable keeps them in step: Compare reports Equal exactly when Equals reports true. Implementing Comparable adds no <; declare the operators for that.

Ordering is an enum stored in an int8, so its discriminant is the sign of the comparison:

pub enum Ordering: int8 {
    Less = -1,
    Equal = 0,
    Greater = 1
}
MemberResult
IsLess(self: &Ordering) -> booltrue for Less
IsEqual(self: &Ordering) -> booltrue for Equal
IsGreater(self: &Ordering) -> booltrue for Greater
IsLessOrEqual(self: &Ordering) -> booltrue for Less or Equal
IsGreaterOrEqual(self: &Ordering) -> booltrue for Greater or Equal
Reverse(self: Ordering) -> OrderingLess and Greater swapped, Equal kept

Hashable

pub interface Hashable {
    func Hash() -> uint64;
}

Hash returns a 64-bit summary of the value. Equal values must hash equally — a table is wrong otherwise — while unequal values may collide. A type that implements Hashable implements Equatable consistently with it, and the same value always gives the same hash within a run.

Iterator and Iterable

pub interface Iterator {}

pub interface Iterable {}

Both are empty. A for loop is driven by shape — a Next method on an iterator, an Iterate method on a container — and the item type differs per iterator, which an interface cannot name. What these interfaces add is the name: extend Countdown : Iterator records that a type means to be an iterator, and a bound written <T: Iterator> documents what a generic expects. Neither is required to iterate, and a bound on either grants no operations. See Iteration for the protocol itself.

Display and Debug

pub interface Display {
    func WriteDisplay(writer: &var TextWriter, spec: FormatSpec) -> ! FormatError;
}

pub interface Debug {
    func WriteDebug(writer: &var TextWriter, spec: FormatSpec) -> ! FormatError;
}

Display is the text a value has for a reader: a number is its digits, a string its characters, nothing quoted. Debug is the text a value has for whoever is inspecting a program: it may quote, escape and name parts. Both write into a destination rather than returning a string, so rendering allocates nothing.

ParameterMeaning
writer: &var TextWriterthe destination — the console, a string builder, a buffer; TextWriter is itself an interface with one requirement, func Write(self: &var Self, bytes: char8[..]) -> ! FormatError;
spec: FormatSpecwhat the placeholder asked for — width, alignment, precision; {} passes FormatSpec::Plain()
-> ! FormatErrora write can fail; an implementation stops at the first failed write and reports it

How PrintLine uses Display

PrintLine and Print from the Io package take their arguments as a variadic parameter of Display interface values:

pub func PrintLine(#Format() format: char8[..], args: Display...) -> IoError?

Each argument is converted to a Display value at the call, and each {} placeholder calls its WriteDisplay. The primitive numbers, characters and booleans and the string types implement Display in the standard packages, which is why they print; a type of your own prints once it implements Display too. An argument that does not is refused:

error: argument 2 to 'PrintLine' has type 'Money', but variadic parameter 'args' requires 'Display'

No placeholder selects Debug. Debug text is written explicitly, with Text::WriteDebugValue(writer, value, spec) or through a bound such as <T: Debug>; Text::WriteValue is the matching entry point for Display.

Example

import Core::{ Comparable, Equatable, Hashable, Ordering };
import Io::PrintLine;
import Text::{ Display, FormatError, FormatSpec, TextWriter, WriteBytes, WriteValue };

struct Version {
    major: int32;
    minor: int32;
}

extend Version : Equatable {
    func Equals(self: &Version, other: &Version) -> bool {
        return self.major == other.major && self.minor == other.minor;
    }
}

extend Version : Comparable {
    func Compare(self: &Version, other: &Version) -> Ordering {
        if self.major != other.major {
            return self.major < other.major ? Ordering::Less : Ordering::Greater;
        }
        if self.minor != other.minor {
            return self.minor < other.minor ? Ordering::Less : Ordering::Greater;
        }
        return Ordering::Equal;
    }
}

extend Version : Hashable {
    func Hash(self: &Version) -> uint64 {
        return (self.major as uint64) << 32 | (self.minor as uint64);
    }
}

extend Version : Display {
    func WriteDisplay(self: &Version, writer: &var TextWriter, spec: FormatSpec) -> ! FormatError {
        WriteBytes(writer, "v")?;
        WriteValue(writer, self.major, spec)?;
        WriteBytes(writer, ".")?;
        return WriteValue(writer, self.minor, spec);
    }
}

func Main() -> int {
    let old = Version { major: 1, minor: 9 };
    let fresh = Version { major: 1, minor: 10 };
    PrintLine("{} equals {}: {}", old, fresh, old.Equals(fresh));
    PrintLine("older: {}", old.Compare(fresh).IsLess());
    PrintLine("newer: {}", old.Compare(fresh).Reverse().IsLess());
    return 0;
}

See also