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 integers, characters and booleans, the floats from float8 to float64, and the string types implement Display in the standard packages, which is why they print. float80 and wider do not, because finding their digits allocates — see Printing the wide widths. A type of your own prints once it implements Display too. An argument that does not fits neither this overload nor the one-value overloads PrintLine also has for the string types and most primitives, so the call is refused as a whole:

error: no matching overload for 'PrintLine' with argument types (char8[..], Money)

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