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.
| Interface | Package | Requirement | Used by |
|---|---|---|---|
Equatable | Core | func Equals(other: &Self) -> bool; | searching, deduplication, hash keys |
Comparable | Core | func Compare(other: &Self) -> Ordering; | sorting, binary search, extremes |
Hashable | Core | func Hash() -> uint64; | hash tables |
Iterator | Core | none | names the iterator role |
Iterable | Core | none | names the container role |
Display | Text | func WriteDisplay(writer: &var TextWriter, spec: FormatSpec) -> ! FormatError; | {} placeholders, PrintLine |
Debug | Text | func 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:
| Law | Meaning |
|---|---|
| Reflexive | a.Equals(a) is true |
| Symmetric | a.Equals(b) is b.Equals(a) |
| Transitive | a.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
}
| Member | Result |
|---|---|
IsLess(self: &Ordering) -> bool | true for Less |
IsEqual(self: &Ordering) -> bool | true for Equal |
IsGreater(self: &Ordering) -> bool | true for Greater |
IsLessOrEqual(self: &Ordering) -> bool | true for Less or Equal |
IsGreaterOrEqual(self: &Ordering) -> bool | true for Greater or Equal |
Reverse(self: Ordering) -> Ordering | Less 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.
| Parameter | Meaning |
|---|---|
writer: &var TextWriter | the 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: FormatSpec | what the placeholder asked for — width, alignment, precision; {} passes FormatSpec::Plain() |
-> ! FormatError | a 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
- Interfaces — implementing an interface
- Operators —
==and<, which these interfaces do not provide - Iteration — the protocol
IteratorandIterablename - Learn: Equatable, Comparable, Display
Interface Values
An interface type holds or borrows any implementing value, and a call through it runs the method of the value inside: dynamic dispatch.
Operators
A type overloads a binary operator by declaring a function named after it. Comparisons derive from == and <, and structures compare field by field.