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 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
- 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.