Struct pattern
Variant showed that a case can carry named fields, declared like a struct's, as in Between { low: float64; high: float64; }. Variant match then took cases apart by position, as in .Jump(across, _). Named fields deserve a pattern that uses their names: with two or three values of the same type, a name says far more than a position — is the second number the height or the base? This lesson shows that pattern.
Cases with named fields
variant Shape {
Circle { radius: int32; },
Rectangle { width: int32; height: int32; },
Triangle { base: int32; height: int32; }
}
A value is built with the same braces, naming each field: Shape::Circle { radius: 5 }. The pattern that matches it looks just like that literal, with names where the values were.
Binding fields by name
The shorthand writes each field alone, and each one arrives as a variable of the same name:
func Area(shape: Shape) -> int32 {
return match shape {
.Circle { radius } => 3 * radius * radius,
.Rectangle { width, height } => width * height,
.Triangle { base, height } => base * height / 2
};
}
A field written alone, radius, is short for radius: radius: "read the radius field into a variable called radius".
Renaming and leaving out
Fields are found by name, so their order in the pattern does not matter, and you are free to skip the ones you do not need:
func Width(shape: Shape) -> int32 {
return match shape {
.Circle { radius: r } => 2 * r,
.Rectangle { width } => width,
.Triangle { height: _, base } => base
};
}
radius: r reads the radius into r. The rectangle leaves its height out, and a field left out matches anything, as if it were written height: _. The triangle writes height: _ anyway — that says "I know about this field and I do not need it" — and lists its fields in the opposite order from the declaration.
Testing a field
A literal in a field's place picks out the cases holding that value:
func Kind(shape: Shape) -> char8[..] {
return match shape {
.Circle { radius: 0 } => "dot",
.Circle {} => "circle",
.Rectangle {} => "rectangle",
.Triangle {} => "triangle"
};
}
Arms are tried in order, so the circle with radius 0 comes first and the plain circle arm below it catches every other radius. The other arms test nothing, and {} leaves every field out.
Four spellings
| Field in the pattern | Means |
|---|---|
radius | bind the field to a variable of the same name |
radius: r | bind the field to a name of your choosing |
height: _ or left out | ignore the field |
radius: 0 | match only when the field holds this value |
The program
The whole lesson is one package in the Examples repository. Its comments explain every step.
// A variant case can carry named fields, declared like a struct's: `Rectangle { width: int32;
// height: int32; }`. The pattern for such a case looks like the literal that builds it, with
// names where the values were: `.Rectangle { width, height } =>`.
//
// Each field in the pattern binds a variable or tests a value. Four spellings cover what you
// usually need:
//
// .Circle { radius } bind the field to a variable of the same name
// .Circle { radius: r } bind the field to a name of your choosing
// .Rectangle { width } leave out a field you do not need
// .Circle { radius: 0 } match only when the field holds this value
//
// Fields are found by name, so their order in the pattern does not matter. A field left out
// matches anything, as if it were written `height: _`, and `.Rectangle {}` leaves out every field.
// Writing `height: _` anyway says "I know about this field and I do not need it".
import Io::PrintLine;
variant Shape {
Circle { radius: int32; },
Rectangle { width: int32; height: int32; },
Triangle { base: int32; height: int32; }
}
// The shorthand: each field arrives under its own name.
func Area(shape: Shape) -> int32 {
return match shape {
.Circle { radius } => 3 * radius * radius,
.Rectangle { width, height } => width * height,
.Triangle { base, height } => base * height / 2
};
}
// Renaming and ignoring. `radius: r` reads the radius into `r`; the rectangle leaves out the
// height this question does not need; and the triangle skips it with `height: _`, listing its
// fields in the opposite order.
func Width(shape: Shape) -> int32 {
return match shape {
.Circle { radius: r } => 2 * r,
.Rectangle { width } => width,
.Triangle { height: _, base } => base
};
}
// Testing. A literal in a field picks out the cases holding that value. Arms are tried in order, so
// the circle with a `0` comes first and the plain circle arm below it catches every other radius.
// The other arms test no field, so `{}` leaves every field out.
func Kind(shape: Shape) -> char8[..] {
return match shape {
.Circle { radius: 0 } => "dot",
.Circle {} => "circle",
.Rectangle {} => "rectangle",
.Triangle {} => "triangle"
};
}
func Main() -> int {
let shapes: Shape[4] = [
Shape::Circle { radius: 5 },
Shape::Rectangle { width: 4, height: 6 },
Shape::Triangle { base: 10, height: 3 },
Shape::Circle { radius: 0 }
];
for index in 0..4 {
PrintLine("{:9} width {:2}, area {}", Kind(shapes[index]), Width(shapes[index]),
Area(shapes[index]));
}
return 0;
}
Run it
cd Examples/Patterns/StructPattern
rux run
circle width 10, area 75
rectangle width 4, area 24
triangle width 10, area 15
dot width 0, area 0
Common mistakes
Fields are found by name, so a name the case does not have is an error:
.Circle { radiuss } => stops with error: unknown field 'radiuss' in variant pattern..Circle => names a case with no data. For a case with fields it fails with error: pattern for 'Shape::Circle' expects 1 field, but found 0. Write .Circle {} to match any circle without reading its fields..Circle { radius: 0 } covers only the circles of radius 0. If no plain .Circle {} (or .Circle { radius }) arm follows, the match fails with error: match on 'Shape' is not exhaustive; missing Shape::Circle.Try it yourself
- Add a
Square { side: int32; }case toShape. Runrux checkfirst and read which matches the compiler sends you to. - Add an arm to
Kindthat names a rectangle whosewidthandheightare equal a"square". You will need a guard. - Write a function
Perimeterthat renames every field it reads, as in.Rectangle { width: w, height: h }.
Learn more
- Variants with data and structs in the Rux Reference
- Variant and Variant match — building and matching cases
- Exhaustive — why every case of
Shapeneeds an arm