Ranges
Zena provides first-class range expressions using the .. operator. Rather than
being a specialized indexing syntax confined to brackets, range expressions
evaluate to first-class objects that can be stored in variables, passed to
functions, and inspected at runtime.
First-class range objects ​
Range expressions construct instances of dedicated range classes defined in
zena:core and included in the standard prelude:
| Expression | Type | Mathematical Notation | Description |
|---|---|---|---|
start..end |
BoundedRange |
[start, end) |
Half-open interval from start (inclusive) to end (exclusive) |
start.. |
FromRange |
[start, ∞) |
Range from start through the end of the collection |
..end |
ToRange |
[0, end) |
Range from index 0 up to end (exclusive) |
.. |
FullRange |
[0, length) |
Full range spanning all elements |
All four classes are part of the Range union type exported from zena:core:
type Range = BoundedRange | FromRange | ToRange | FullRange;
Because ranges are ordinary objects, they can be bound to variables, passed as arguments, and inspected:
let r: BoundedRange = 10..20;
println(r.start); // 10
println(r.end); // 20
let from = 5..;
println(from.start); // 5
let to = ..15;
println(to.end); // 15
let all = ..; // FullRange
Range syntax and precedence ​
The range operator .. supports four syntactic forms:
let bounded = 1..10; // BoundedRange
let postfix = 5..; // FromRange
let prefix = ..8; // ToRange
let bare = ..; // FullRange
Operand types ​
Both the start and end operands of a range expression must evaluate to 32-bit
signed integers (i32):
let start = 2;
let end = 7;
let dynamicRange = start..end; // BoundedRange(2, 7)
// Non-i32 operands produce compile-time type errors:
let invalid = 'a'..'z';
Error:
Planned support for generalized operand types
Currently, range expressions require i32 operands. Zena plans to relax this
constraint to support generic range bounds Range<T>, drawing inspiration from
range models in languages such as Swift and Rust:
- Discrete ordered types: Types with a discrete ordering where every element
has a well-defined immediate successor (often termed enumerable,
steppable, or discrete ordered types) can support range formation and
sequential iteration. A key candidate is a future Unicode-safe string index
type (similar to Swift's
String.Index), enabling expressive string slicing likestr[start..end]. An interface (such asSteppable<T>orEnumerable<T>) will allow ranges over any discrete type to be iterated automatically. - Continuous and non-iterable types: Range expressions can also encompass
types that have an ordering but no discrete successor, such as floating-point
numbers (
f32,f64). While continuous ranges cannot be iterated without an explicit stride, they enable interval operations, containment checks (range.contains(val)), clamping, and custom-stepped iteration (for example, progressing through0.0..1.0by steps of0.1or1.0).
Operator precedence ​
The .. operator has lower precedence than arithmetic, bitwise, and comparison
operators. Expressions on either side of .. evaluate before the range is
constructed, eliminating the need for grouping parentheses in dynamic
calculations:
let offset = 4;
let count = 6;
// Evaluates as (offset + 1)..(offset + count):
let window = offset + 1..offset + count; // BoundedRange(5, 10)
Slicing with ranges ​
Ranges are primarily used for slicing collections via the index operator [].
Slicing arrays ​
The Array<T> interface and all concrete array classes (FixedArray<T>,
ImmutableArray<T>, and GrowableArray<T>) implement overloads of operator [] for all four range types, returning a new array containing a shallow copy of
the selected elements:
let items = [10, 20, 30, 40, 50];
// BoundedRange: items from index 1 up to 4:
let slice1 = items[1..4]; // [20, 30, 40]
// FromRange: items from index 2 through the end:
let slice2 = items[2..]; // [30, 40, 50]
// ToRange: items from index 0 up to 3:
let slice3 = items[..3]; // [10, 20, 30]
// FullRange: shallow copy of the entire array:
let copy = items[..]; // [10, 20, 30, 40, 50]
Slicing an array with a range clamps bounds outside the array to its valid
length, and inverted ranges (where start >= end) produce an empty array.
Current limitations and planned work ​
Ranges are not yet supported on every collection type:
- Strings:
Stringcurrently supports slicing through method calls such asstr.slice(start, end). Slicing strings with range syntax (str[a..b]) is planned once a Unicode-safe design is finalized. Taking inspiration from Swift'sString.Index, this will use dedicated index positions that navigate code-point or grapheme-cluster boundaries, ensuring string ranges cannot slice across invalid byte sequences.
Range iteration ​
BoundedRange and FromRange implement Iterable<i32>. You can loop over a
range directly using for-in syntax:
for (let i in 0..10) {
println(i);
}
A for-in loop over a range compiles to the same efficient counting loop as
for (var i = 0; i < 10; i += 1). The range bounds are evaluated once before
the loop.
Ranges held in variables can be iterated the same way:
let r = 1..5;
for (let i in r) {
println(i);
}
Because BoundedRange and FromRange implement Iterable<i32>, they also
support standard iterable methods such as .filter(), .map(), and .fold():
let sum = (0..10).fold(0, (total, i) => total + i);