Literal types
`"tcp" | "udp"` is a type made only of specific values of one base type; only literals combine with `|`, because there are no unions of types.
A literal type is a union of literals of one base type - String, Int or Char. It is for "one of these exact
values" in a signature, a configuration field or a wire format, and it is told apart from any other value of the same
base type by generated members, the same way a declared type is.
Example#
type Status = "online" | "offline" | "away"
var status: Status = "online"
status = "away"
const text = "online"
status = Status.tryFrom(text) ?? "offline"
const label = match status {
"online" => "here"
"offline" => "gone"
"away" => "back soon"
}
print "{status} {label}"Syntax#
type Name = "a" | "b" | "c" a literal type of Strings
type Name = 0 | 1 | 3 a literal type of Ints
fn f(mode: "tcp" | "udp" = "tcp") { ... } inline, in a signatureRules#
A literal type is a union of literals of one base type.
String,IntandCharare the base types this works for; the members are told apart by value, not by a type of their own.A literal has its ordinary type unless a literal type is expected.
"online"alone is aString; it becomes aStatusonly whereStatusis the expected type, the same way an integer literal adapts toInt8.matchon a literal type is exhaustive without a wildcard_. Every arm names one of the members, and that is already every value the type has.parse,Show,Equals,Hash,EncodeandDecodeare generated for every literal type, the same list a declaredtypegets.parsereturnsResult<Self, LiteralParseError>; going back to the base type is interpolation or.into().Decodereads the base and then checks it against the members, with the sameLiteralParseError- the base's owndecodewould let any text in.Two literal types with the same members are the same type. The identity is structural, so a literal type has no owner of its own the way a declared
typedoes.Only literals combine with
|.Int | Stringis not a type: there are no unions of types in this language, because nothing at runtime distinguishes them. A type with cases is the tool once behavior or non-literal data is needed.Does not compiletype Width = Int | String // error: Only literals can be combined with `|` (`"online" | "offline"`). There are no unions of types: declare a type with cases
What this is not#
A literal type is not extendable. extend is rejected at its own line, before the member it would have added is
ever looked up.
type Status = "online" | "offline" | "away"
const status: Status = "online"
print status.show()type Status = "online" | "offline" | "away"
extend Status {
fn shout(): String {
"status"
}
}
// error: `"away" | "offline" | "online"` cannot be extendedA literal type is not a subtype of a smaller literal type, or the other way around. "online" | "offline" and
"online" | "offline" | "away" are two different types with no assignability between them; passing one where the
other is expected needs an explicit match or a fresh literal.
Related#
- Type aliases -
type Name = Other, the syntax a literal type reuses. - Distinct types - a type for one case with data, when a literal type stops being enough.
- Bindings - how a literal adapts to an expected type in general.