Destructuring: Tuples, Arrays, Structs and Enums
Patterns are not just for literals: they can reach inside structures and extract values in a single step, without manual access by index or field.
Tuples and arrays in let
In Zolo, positional destructuring uses the form let (a, b, c) = [...].
The names receive the values in order, and the compiler checks the size.
Works in plain let, in for and when receiving function return values.
Positional destructuring in let, function return and for loop.
// Feature: Tuple / array destructuring in `let`
// Syntax: `let (a, b, c) = [v1, v2, v3]`
// When to use: extract multiple values positionally. In Zolo,
// "tuples" at the value site are arrays.
// Basic form: 3 named values.
let (a, b, c) = [1, 2, 3]
print(a) // 1
print(b) // 2
print(c) // 3
// Heterogeneous types: each slot can be a different type.
let (name, age, active) = ["Alice", 30, true]
print(name) // Alice
print(age) // 30
print(active) // true
// Destructuring a function returning an array — min/max pattern.
fn min_max(arr: [int]) -> [int] {
var lo = arr[0]
var hi = arr[0]
for x in arr {
if x < lo { lo = x }
if x > hi { hi = x }
}
return [lo, hi]
}
let (lo, hi) = min_max([3, 1, 4, 1, 5, 9, 2, 6])
print("lo={lo} hi={hi}") // lo=1 hi=9
// Destructuring inside `for`.
let pairs = [[1, 2], [3, 4], [5, 6]]
for pair in pairs {
let (l, r) = pair
print("{l},{r}")
}
// expected:
// 1
// 2
// 3
// Alice
// 30
// true
// lo=1 hi=9
// 1,2
// 3,4
// 5,6
Array patterns with ..
Beyond destructuring a fixed number of elements, you can separate the first
(or last) element(s) from the rest with ..rest. rest becomes a new array.
.. without a name simply ignores the remaining elements and is used to
verify the shape of the array.
[first, ..rest], [a, b, ..], empty array and minimum-length check.
// Feature: Array patterns with rest `..`
// Syntax: `[first, ..rest]`, `[a, b, ..]`, `[..init, last]`
// When to use: separate the first/last element(s) from the "rest"
// without index arithmetic.
let arr = [1, 2, 3, 4, 5]
// First + rest.
match arr {
[first, ..rest] => print("first={first} rest_len={rest.len()}"),
_ => print("empty"),
}
// First two + rest.
match arr {
[a, b, ..rest] => print("a={a} b={b} rest_len={rest.len()}"),
_ => print("fewer than 2"),
}
// Without naming the rest — only checks shape.
match arr {
[_, _, ..] => print("has at least 2"),
_ => print("does not have 2"),
}
// Empty array.
let empty: [int] = []
match empty {
[] => print("really empty"),
[_, ..] => print("has at least 1"),
}
// expected:
// first=1 rest_len=4
// a=1 b=2 rest_len=3
// has at least 2
// really empty
Struct destructuring
Inside a match, the pattern StructType { field1, field2, .. } extracts
fields by name. .. ignores the remaining fields without listing them.
Guards can be added after the pattern to refine the selection.
Extracting fields from Point and Person; guard on an extracted field.
// Feature: Struct destructuring
// Syntax: `Type { field1, field2, .. }`
// When to use: extract specific fields from a struct inside a
// match or let. The `..` ignores the remaining fields.
struct Point {
x: float,
y: float,
z: float,
}
let p = Point { x: 1.0, y: 2.0, z: 3.0 }
// Take everything by name.
match p {
Point { x, y, z } => print("({x},{y},{z})"),
}
// Just some fields — `..` ignores the rest.
match p {
Point { x, .. } => print("only x = {x}"),
}
match p {
Point { y, z, .. } => print("y={y} z={z}"),
}
// Direct struct destructure in `let` is not yet supported in
// pattern position — use `match` when you need a full pattern,
// or field access (`p.x`) for the simple case.
print("p.x = {p.x}")
print("p.y = {p.y}")
// Match with guard on fields.
struct Person {
name: str,
age: int,
}
let alice = Person { name: "Alice", age: 17 }
let label = match alice {
Person { age, .. } if age >= 18 => "adult",
Person { age, .. } => "minor ({age})",
}
print(label) // minor (17)
// expected:
// (1,2,3)
// only x = 1
// y=2 z=3
// p.x = 1
// p.y = 2
// minor (17)
Note: direct struct destructuring in
letposition is not yet supported — usematchor field access (p.x) in those cases.
Enum destructuring
Each enum variant can carry a payload (positional or with named fields).
The pattern extracts that payload directly. A match over an enum is
exhaustive by construction: the compiler requires an arm for every variant.
Shape with tuple and struct variants; Event with named payload.
// Feature: Enum destructuring (with payload)
// Syntax: `Enum.Variant(x, y)` or `Enum.Variant { field }`
// When to use: extract values from each variant in a match.
enum Shape {
Circle(float),
Rect(float, float),
Triangle {
a: float,
b: float,
c: float,
},
Empty,
}
fn area(s: Shape) -> float {
return match s {
Shape::Circle(r) => 3.14159 * r * r,
Shape::Rect(w, h) => w * h,
Shape::Triangle { a, b, c } => {
let semi = (a + b + c) / 2.0
return semi
},
Shape::Empty => 0.0,
}
}
print(area(Shape.Circle(5.0))) // 78.53975
print(area(Shape.Rect(3.0, 4.0))) // 12
// Struct-like variant constructor uses `::`.
print(area(Shape::Triangle { a: 3.0, b: 4.0, c: 5.0 })) // 6
print(area(Shape.Empty)) // 0
// Enum with struct-like variants — named-field pattern.
enum Event {
Click {
x: int,
y: int,
},
Key(str),
Quit,
}
fn describe(e: Event) -> str {
return match e {
Event::Click { x, y } => "click at ({x},{y})",
Event::Key(k) => "key {k}",
Event::Quit => "quit",
}
}
print(describe(Event::Click { x: 100, y: 200 }))
print(describe(Event.Key("Enter")))
print(describe(Event.Quit))
// expected:
// 78.53975
// 12
// 6
// 0
// click at (100,200)
// key Enter
// quit
Challenge
Add a new Point(float, float) variant to the Shape enum in the enums example
and handle it in the area function. Notice how the compiler warns about
exhaustiveness before you add the arm.
See also