Enum value
Behind every enum case is a number. Usually nobody needs to know it — the Enum lesson never mentioned it. But sometimes the number is the point: a web server answers 404 for "not found", and a program that reads or writes such codes needs its cases to be exactly those numbers.
An enum can say how its numbers are stored, with an underlying type after a colon, and which number each case has. as then converts between a case and its number, in both directions.
Underlying type and explicit numbers
enum Status: uint16 {
Ok = 200,
Created,
NotFound = 404,
ServerError = 500
}
: uint16 stores each Status in a uint16. A case may give its number with =. A case without a number of its own takes the one after the case before it, so Created is 201. With no numbers given at all, the cases count up from zero — which is why Direction::South as int would be 2.
flowchart LR
ok["Ok = 200"] -- "+1" --> cr["Created<br/>201"]
cr -. "set explicitly" .-> nf["NotFound = 404"]
nf -. "set explicitly" .-> se["ServerError = 500"]From a case to its number, and back
PrintLine("Ok is {}", Status::Ok as uint16);
let received: uint16 = 404;
let status = received as Status;
A case is not its number, though. Status::Ok == 200 is refused: compare a case with a case, or convert first.
Cases are ordered
Cases order by their numbers, so a range of codes is a pair of comparisons:
PrintLine("NotFound is an error: {}", status >= Status::NotFound);
as does not check
Here is the surprise. 418 as Status compiles and runs, and gives a Status that is none of its four cases. Nothing goes wrong until something asks which case it is — then a match naming all four has no arm for it, and the program stops with Panic: no match arm matched value of 'Status'.
So a number from outside — a file, the network, the user — is checked before it becomes a case:
func IsKnown(code: uint16) -> bool {
return match code {
200 => true,
201 => true,
404 => true,
500 => true,
else => false
};
}
flowchart LR
n["A uint16 from outside"] --> k{"IsKnown(code)?"}
k -- "yes" --> s["code as Status<br/>a real case"]
k -- "no" --> r["Reject it: report,<br/>or use a fallback"]Numbers are a promise
Once other programs read these numbers, they are part of your interface. Inserting a case without a number in the middle of the list would quietly renumber every unnumbered case after it. When the numbers matter, write them all out.
The program
The whole lesson is one package in the Examples repository. Its comments explain every step.
// Behind every enum case is a number. Usually nobody needs to know it, but sometimes the number is
// the point: a web server answers 404 for "not found", and a program that reads or writes such
// codes needs its cases to be exactly those numbers.
//
// An enum can say how its numbers are stored, with an underlying type after a colon, and which
// number each case has. `as` converts between a case and its number, in both directions.
import Io::PrintLine;
// A case without a number of its own takes the one after the case before it, so `Created` is 201.
// With no numbers given at all, the cases count up from zero.
enum Status: uint16 {
Ok = 200,
Created,
NotFound = 404,
ServerError = 500
}
// A code read from outside should be checked before it becomes a `Status`, because `as` trusts it.
func IsKnown(code: uint16) -> bool {
return match code {
200 => true,
201 => true,
404 => true,
500 => true,
else => false
};
}
func Main() -> int {
// From a case to its number.
PrintLine("Ok is {}", Status::Ok as uint16);
PrintLine("Created is {}", Status::Created as uint16);
PrintLine("NotFound is {}", Status::NotFound as uint16);
PrintLine("ServerError is {}", Status::ServerError as uint16);
// From a number to a case.
let received: uint16 = 404;
let status = received as Status;
PrintLine("{} means not found: {}", received, status == Status::NotFound);
// Cases order by their numbers, so a range of codes is a pair of comparisons.
PrintLine("NotFound is an error: {}", status >= Status::NotFound);
PrintLine("Created is an error: {}", Status::Created >= Status::NotFound);
// The surprise: `as` does not check. `418 as Status` compiles and runs, and gives a `Status`
// that is none of its four cases. A `match` naming all four has no arm for it, so the program
// stops there with `Panic: no match arm matched value of 'Status'`. So check first.
let strange: uint16 = 418;
PrintLine("{} is a known status: {}", received, IsKnown(received));
PrintLine("{} is a known status: {}", strange, IsKnown(strange));
// The numbers are now a promise to whoever reads them. Inserting a case without a number in
// the middle of the list would quietly renumber the ones after it.
return 0;
}
Run it
cd Examples/Types/EnumValue
rux run
Ok is 200
Created is 201
NotFound is 404
ServerError is 500
404 means not found: true
NotFound is an error: true
Created is an error: false
404 is a known status: true
418 is a known status: false
Common mistakes
as with an unchecked number.as never refuses. 418 as Status makes a value that no match arm fits, and the program stops later with Panic: no match arm matched value of 'Status' — far from the line that made it. Check the number first, as IsKnown does.Status::Ok == 200 fails with error: operator '==' cannot compare left operand 'Status' with right operand 'int'. Write Status::Ok as uint16 == 200, or compare with another case.Add
Accepted between Ok and Created, and Accepted becomes 201 while Created moves to 202 — no error, just different numbers. Give every case whose number matters an explicit =.Try it yourself
- Add
Accepted = 202andNoContent = 204, updateIsKnown, and print their numbers. - Write
func IsError(self: Status) -> boolin anextend Statusblock, using a comparison rather than amatch. - Write
func Name(self: Status) -> char8[..]and use it to print the name ofreceived as Status. What happens if you call it onstrange as Status? - Declare
enum Level: uint8 { Low, Medium, High }and print each case's number.
Learn more
- Backing type and explicit values in the Rux Reference
- Convert —
asbetween numeric types - Variant — cases that carry data
- Checked convert — conversions that report whether the value fits