Display
PrintLine("{}", value) has printed numbers, text and booleans since the very first lessons. That is not special treatment for built-in types: each of them implements one interface, Display from the Text package. A type of your own prints the same way as soon as it implements Display too. This lesson gives a Money type the text $12.05.
The Display interface
Display asks for a single method. In the Text package it is declared as:
func WriteDisplay(writer: &var TextWriter, spec: FormatSpec) -> ! FormatError;
| Part | What it is |
|---|---|
writer: &var TextWriter | Wherever the text is going — the console, a string, a buffer. An interface parameter, borrowed writable because writing moves it along. |
spec: FormatSpec | What the placeholder asked for, such as the width in {:>8}. |
-> ! FormatError | Writing can fail — the console may be closed, a buffer may be full — so the method is a unit fallible. |
These names come from two packages, so the lesson imports them, and its Rux.toml lists Format and Text:
import Format::WriteFormat;
import Io::PrintLine;
import Text::{ Display, FormatError, FormatSpec, TextWriter, WriteBytes };
Writing the text
extend Money : Display {
// `spec` carries what the placeholder asked for, such as the width in `{:>8}`. This
// implementation ignores it, which a type is free to do.
func WriteDisplay(self: &Money, writer: &var TextWriter, spec: FormatSpec) -> ! FormatError {
var cents = self.cents;
if cents < 0 {
// One write that may fail. `?` hands a failure to the caller and stops here.
WriteBytes(writer, "-")?;
cents = -cents;
}
// `WriteFormat` is PrintLine's twin: the same placeholders, written into the writer.
// `{:02}` pads the cents to two digits with zeros, so 5 cents shows as `05`.
return WriteFormat(writer, "${}.{:02}", cents / 100, cents % 100);
}
}
The method is built from two writes, and either may fail:
WriteBytes(writer, "-")?writes the minus sign. The?from Propagate passes a failure straight back to the caller and stops there.return WriteFormat(…)writes the rest and hands back whatever that write reported — success or failure.
WriteFormat takes the same placeholders as PrintLine. Here {:02} pads the cents to two digits, so 5 cents becomes 05 and the coin prints as $0.05.
What happens when you print
flowchart LR
p["PrintLine with a {}<br/>and a Money"] --> d["Money's WriteDisplay"]
d --> w1["WriteBytes: '-'<br/>(only when negative)"]
w1 --> w2["WriteFormat: '$12.05'"]
w2 --> out["the console"]
w1 -.->|"a failed write<br/>returns early"| pPrintLine checks every argument against Display — its arguments are, in effect, a list of interface values. Money now passes, so it goes into a placeholder like any number would:
PrintLine("price: {}", price);
PrintLine("refund: {}", refund);
PrintLine("coin: {}", coin);
PrintLine("{} back from {}", refund, price);
Ignoring the spec
spec carries the placeholder's width, alignment, fill and precision. Money ignores it, which a type is free to do, but it has a visible effect: PrintLine("[{:>10}]", price) prints [$12.05] with no padding, because nothing in WriteDisplay applies the width. The Format lesson covers what those placeholder options mean.
The program
The whole lesson is one package in the Examples repository. Its comments explain every step.
// `PrintLine("{}", value)` prints numbers, text and booleans because each of those types
// implements one interface, `Display` from the Text package. A type of your own prints the same
// way as soon as it implements `Display` too.
//
// `Display` asks for a single method, `WriteDisplay`. It is handed a writer, which stands for
// wherever the text is going, and it writes the value's text into it. Writing can fail (the
// console may be closed, a buffer may be full), so the method returns `! FormatError`, and a
// failure from any step is passed straight on with `?` or `return`.
import Format::WriteFormat;
import Io::PrintLine;
import Text::{ Display, FormatError, FormatSpec, TextWriter, WriteBytes };
// An amount of money, kept as whole cents so nothing is lost to rounding.
struct Money {
cents: int64;
}
extend Money : Display {
// `spec` carries what the placeholder asked for, such as the width in `{:>8}`. This
// implementation ignores it, which a type is free to do.
func WriteDisplay(self: &Money, writer: &var TextWriter, spec: FormatSpec) -> ! FormatError {
var cents = self.cents;
if cents < 0 {
// One write that may fail. `?` hands a failure to the caller and stops here.
WriteBytes(writer, "-")?;
cents = -cents;
}
// `WriteFormat` is PrintLine's twin: the same placeholders, written into the writer.
// `{:02}` pads the cents to two digits with zeros, so 5 cents shows as `05`.
return WriteFormat(writer, "${}.{:02}", cents / 100, cents % 100);
}
}
func Main() -> int {
let price = Money { cents: 1205 };
let refund = Money { cents: -350 };
let coin = Money { cents: 5 };
// Now Money goes into a placeholder like any number would.
PrintLine("price: {}", price);
PrintLine("refund: {}", refund);
PrintLine("coin: {}", coin);
PrintLine("{} back from {}", refund, price);
// Take away the `extend` and every line above is refused: "argument 2 to 'PrintLine' has
// type 'Money', but variadic parameter 'args' requires 'Display'".
return 0;
}
Besides Io, its Rux.toml lists Format and Text under [Dependencies].
Run it
cd Examples/Interfaces/Display
rux run
price: $12.05
refund: -$3.50
coin: $0.05
-$3.50 back from $12.05
Common mistakes
Take away the
extend and every PrintLine with a Money in it is refused: error: argument 2 to 'PrintLine' has type 'Money', but variadic parameter 'args' requires 'Display'.WriteBytes(writer, "-"); without the ? fails with error: fallible result of type '! FormatError' is discarded. Every write can fail, so each one is either passed on with ? or returned.return on the last write.WriteFormat(writer, …); on its own line is a discarded fallible too, with the same error. Write return WriteFormat(…);, so its success or failure becomes the method's own.Try it yourself
- Give
struct Point { x: int32; y: int32; }aDisplaythat prints(1, 2). - Print
pricewithPrintLine("[{:>10}]", price)and explain why no padding appears. - Make a
Temperaturewith acelsius: int32field print as21 C, and-4 Cbelow zero.