Variant
An enum says a value is one of a fixed list of cases. A variant says the same, and lets each case carry data of its own. A thermometer reading might be missing, or an exact temperature, or a range between two temperatures. Those are three cases, and two of them hold numbers.
A struct holds all of its fields at once. A variant holds one case at a time, and only that case's data. This lesson declares and builds variants; reading the data back out is the next lesson.
Three shapes of case
variant Reading {
Missing,
Exact(float64),
Between { low: float64; high: float64; }
}
| Case | Shape | Carries | Built as |
|---|---|---|---|
Missing | a bare name, like an enum case | nothing | Reading::Missing |
Exact | values by position, like a tuple | one float64 | Reading::Exact(21.5) |
Between | named fields, like a struct | low and high | Reading::Between { low: 20.0, high: 24.0 } |
A positional case may carry several values — Jump(int, int) in the next lesson carries two.
Struct, enum, variant
flowchart LR
s["struct Point<br/>x AND y,<br/>always both"]
e["enum Direction<br/>North OR East OR …,<br/>no data"]
v["variant Reading<br/>Missing OR Exact(t)<br/>OR Between { low, high }"]
s -. "fields" .-> v
e -. "a fixed list of cases" .-> vA variant borrows from both: the "one of a list" of an enum, and the data of a struct or tuple — but only for the case that is held.
Choosing the case
A variant is a type like any other, so a function can return one, choosing the case as it goes:
func Measure(low: float64, high: float64) -> Reading {
if low > high {
return Reading::Missing;
}
if low == high {
return Reading::Exact(low);
}
return Reading::Between { low: low, high: high };
}
Every return gives back a Reading; which case it is depends on the numbers.
Comparing variants
Two variant values are equal when they hold the same case with the same data. Different cases are never equal, whatever they carry:
PrintLine("same case, same data {}", precise == Reading::Exact(21.5));
PrintLine("different cases {}", precise == absent);
No number behind the case
Unlike an enum with values, a variant has no number behind its cases that as could reach. Which case is held is the variant's own business. A value that must be written to a file or sent elsewhere as a number is encoded with an enum, whose numbers you choose.
The program
The whole lesson is one package in the Examples repository. Its comments explain every step.
// An enum says a value is one of a fixed list of cases. A variant says the same, and lets each
// case carry data of its own. A thermometer reading might be missing, or an exact temperature, or
// a range between two temperatures. Those are three cases, and two of them hold numbers.
//
// A struct holds all of its fields at once. A variant holds one case at a time, and only that
// case's data. This lesson declares and builds variants; reading the data back out is the next.
import Io::PrintLine;
// A case can take one of three shapes. `Missing` carries nothing, like an enum case. `Exact`
// carries one value by position, like a one-element tuple. `Between` carries named fields,
// declared like a struct's.
variant Reading {
Missing,
Exact(float64),
Between { low: float64; high: float64; }
}
// A variant is a type like any other: a function can return one, choosing the case as it goes.
func Measure(low: float64, high: float64) -> Reading {
if low > high {
return Reading::Missing;
}
if low == high {
return Reading::Exact(low);
}
return Reading::Between { low: low, high: high };
}
func Main() -> int {
// Each case is built in its own shape: just its name, its values in parentheses, or its
// fields in braces.
let absent = Reading::Missing;
let precise = Reading::Exact(21.5);
let range = Reading::Between { low: 20.0, high: 24.0 };
// Two variant values are equal when they hold the same case with the same data. Different
// cases are never equal.
PrintLine("same case, same data {}", precise == Reading::Exact(21.5));
PrintLine("same case, different data {}", precise == Reading::Exact(9.0));
PrintLine("different cases {}", precise == absent);
PrintLine("same fields {}",
range == Reading::Between { low: 20.0, high: 24.0 });
PrintLine("Measure(5.0, 5.0) exact {}", Measure(5.0, 5.0) == Reading::Exact(5.0));
PrintLine("Measure(9.0, 1.0) missing {}", Measure(9.0, 1.0) == Reading::Missing);
// Unlike an enum, a variant has no number behind its cases that `as` could reach: which case
// is held is the variant's own business. A value that must be written to a file or sent
// elsewhere is encoded with an enum, whose numbers you choose.
return 0;
}
Run it
cd Examples/Types/Variant
rux run
same case, same data true
same case, different data false
different cases false
same fields true
Measure(5.0, 5.0) exact true
Measure(9.0, 1.0) missing true
Common mistakes
precise.0 fails with an error that the type Reading has no field 0. A variant has no fields of its own to read: the data belongs to one case, and which case is held is only known at run time. Taking it out is a job for match, in Variant match.Reading::Between { low: 1.0 } fails with error: initializer for 'Reading::Between' is missing required field 'high' — the same rule as a struct literal.Reading::Exact(1) fails with error: argument 1 to variant case 'Reading::Exact' has type 'int', but field 1 requires 'float64'. A whole-number literal never becomes a float on its own; write 1.0.as.precise as int fails with an error that the variant cannot be cast to the scalar type int. Only an enum's cases have numbers.Try it yourself
- Add a case
Faulty(int)that carries an error code, and build one. - Change
Measureso that a range narrower than0.5counts asExactat its midpoint. - Declare
variant Shape { Circle(float64), Rectangle { width: float64; height: float64; } }and build one of each. - Check whether
Reading::Between { low: 1.0, high: 2.0 }equalsReading::Between { low: 2.0, high: 1.0 }. Predict first.
Learn more
- Variants with data in the Rux Reference
- Variant match — taking the data back out
- Struct and Enum — the two ideas a variant combines
- Sum type — a value that is one of several types