Delegation with by
by forwards a trait's required members to the one field of a single-field type, binding only to the trait or parenthesised group written directly in front of it.
by forwards a trait to the one field of a type, so a wrapper around a single value does not have to write out
members that only ever call through to it.
Example#
type Seconds with Show, (Add, Subtract) by value, Compare by value {
value: Int
}
const total = Seconds(5) + Seconds(2)
print total
print total.max(Seconds(10))Syntax#
type <Name> with <Trait> by <field> { ... }
type <Name> with <Trait>, (<Trait>, <Trait>) by <field> { ... } `by` binds to the group right before itRules#
by <field>forwards the trait written directly in front of it to that field. In(Add, Subtract) by value,valueis the field that providesaddandsubtract.bybinds to one element of thewithlist - the trait or the parenthesised group directly before it - never to the whole list.Show, (Add, Subtract) by value, Compare by valuederivesShow, delegatesAddandSubtracttovaluein one group, and delegatesComparetovalueon its own; nothing aboutbyreaches back toShow. A group needs at least one trait and abyafter it -(Show)alone is the same as writingShow, and a group withoutbyis an error:Does not compiletrait Loud { fn shout(): String } trait Quiet { fn whisper(): String } type Wrapper with (Loud, Quiet) { value: Int fn shout(): String { "loud" } fn whisper(): String { "quiet" } } // error: A group without `by` is an error: write the traits without parenthesesA
withlist separates its entries with commas only -&, which combines traits into one type everywhere a type is expected (Trait intersections), is not a separator here and is a parse error that names the fix:Does not compiletype Wrapper with Add & Subtract by value { value: Int } // error: Traits delegated to the same field are grouped with parentheses, not `&`: write `(Add, Subtract) by value`Does not compiletrait Loud { fn shout(): String } type Wrapper with Loud, Add by value { value: Int } // error: `Wrapper` implements `Loud` but has no `shout`byneeds a type with exactly one field, and the name afterbynames that field. A type with two fields has no single fieldAddcould mean, and a type with cases has no field of its own to name at all.byforwards the required members; the default members still come from the trait.total.max(Seconds(10))isCompare.max, a default that is written in terms ofcompare- the forwarded member - so it returns aSecondsand nothing has to be rewrapped by hand. A constant the trait requires is forwarded the same way:Seconds.zeroof astatic zero: Selfis the field type'sInt.zerowrapped into aSeconds, a constant of another type is the field type's value as it is, and a constant the trait gives a default is that default, read withSelfas the wrapper.A trait that is neither derived, delegated nor written by hand is not available on the wrapper.
Secondshas noMultiply, soSeconds(5) * Seconds(2)does not type check - which is the point, since multiplying two durations is not another duration.
What this is not#
by is not a way to expose the field. value still follows the usual field rules - private value: Int stays
unreadable from outside - by only wires the trait's members to it internally.
type Email with Show by value, TryFrom<String, String> {
private value: String
static fn tryFrom(text: String): Result<Email, String> {
if !text.contains("@") {
return Fail "'{text}' is not an email address"
}
Ok Self(text)
}
}
const email = Email.tryFrom "info@example.test"
print emailA field that is private is readable in the file that declares it and nowhere else, whatever by wires to it -
Path of std/path delegates nothing and hides its parts the same way:
use Path from "std/path"
const path = Path.from "a/b"
print path.componentValues
// error: `componentValues` is private to `Path`Related#
- Traits -
withat the declaration, and wherebysits inside a list of traits. - Coherence and blanket implementations - what still applies to a delegated implementation.