Raku Behind the Docs All corners
Collections · Chapter 11

Ranges

A Range is two endpoints and two flags, and almost everything it does, from counting and smartmatching to arithmetic and printing, is worked out from those four facts rather than from its elements.

42 corners · 51 examples

A Range such as 1..10 stores very little: a start, an end, and whether each of them is excluded. It does not hold its elements. It computes them when it is iterated, counts them with arithmetic when it can, and answers most questions (is a value inside? is one range inside another? what is the smallest element?) from the endpoints alone. Most of the surprises in this chapter come from the gap between those endpoints and the elements a reader pictures.

The precedence of .. and the reason 1..2..3 does not compile are in Who Takes the Operand. The sequence operator ..., which looks similar but deduces a step and takes a list of seeds, is in The Sequence Operator. The list methods that a Range shares with every other list, map, grep, head and the rest, are in Lists, Arrays, Seqs and Slips. This chapter is about what is particular to ranges.

11.1 Four operators and a prefix build a range

.. includes both endpoints. A caret beside the dots excludes the endpoint on its side: ^.. drops the start, ..^ the end, ^..^ both. The prefix ^ builds a range from 0 up to, but not including, its operand, which it first turns into a number: a string is read as one, and an array counts its elements. A * at either end stands for infinity.

say (1^..5).list;
say (1..^5).list;
say (1^..^5).list;
say (^5).raku;
my @names = <x y z>;
say (^@names).raku;
say (^"4").raku;
say (1..*).raku;
say (*..1).raku;
Reference output
(2 3 4 5)
(1 2 3 4)
(2 3 4)
^5
^3
^4
1..Inf
-Inf..1

^@names is the idiom for the indices of an array. The * is not kept: 1..* is stored as 1..Inf, and the two are the same value.

11.2 .Str lists the elements, while say shows the endpoints

A Range has two printed forms. .raku, and .gist, which say uses, write the endpoints joined by the operator. .Str, used by put, by ~ and by interpolation into a string, writes the elements joined by spaces, so an empty range becomes an empty string. An endless range cannot list its elements, and its .Str writes * for the open end instead.

my $r = 1..5;
say $r;
put $r;
say "[$r]";
say "[{3..2}]";
say "a".."c";
say (1..*).Str;
say (1^..^*).Str;
Reference output
1..5
1 2 3 4 5
[1 2 3 4 5]
[]
"a".."c"
1..*
1^..^*

.raku writes each endpoint in its own .raku form, so the types stay visible. The short form ^N is used only for an Int range from 0 with its end excluded:

say (0..^5).raku;
say (0..^5.0).raku;
say (0e0..^5).raku;
say (1.5..2).raku;
say (^4.5).raku;
Reference output
^5
0..^5.0
0e0..^5
1.5..2
0..^4.5

11.3 A Range, a Seq or a Complex cannot be an endpoint

Building a range checks its endpoints. Another Range, a Seq or a Complex number is refused with X::Range::InvalidArg, whose .got attribute holds the offending endpoint; for a Seq it holds the type object Seq, not the sequence. Range.new refuses the same values.

my $inner = ^20;
try 10 .. $inner;
say $!.^name;
say $!.message;
try 1 .. (1, 2).Seq;
say $!.got.raku;
my $z = 5i;
try 1 .. $z;
say $!.message;
Reference output
X::Range::InvalidArg
Range objects are not valid endpoints for Ranges
Seq
Complex objects are not valid endpoints for Ranges
The editor’s engine, Raku++, prints something else here
X::Range::InvalidArg
Range objects are not valid endpoints for Ranges
(1, 2).Seq
Complex objects are not valid endpoints for Ranges

Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.

A Seq endpoint turns up more often than it seems, through precedence: see below.

11.4 1 .. * + 1 is code that makes a range, not a rangeTrap

A bare * at an end means infinity. A * with an operator attached is a Whatever expression, and the whole range expression becomes a WhateverCode: a small function that builds the range when it is called. That is what makes @a[1 .. *-2] work, since a subscript calls such code with the number of elements. ^* is code as well.

my $make = 1 .. * + 1;
say $make.^name;
say $make(4).raku;
say (^*)(3).raku;
my @l = <a b c d e>;
say @l[1 .. *-2];
say (1 .. * + 1).^name;
Reference output
WhateverCode
1..5
^3
(b c d)
WhateverCode.new
The editor’s engine, Raku++, prints something else here
WhateverCode
1..5
^3
(b c d)
WhateverCode

Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.

The last line is not a misprint. Parentheses do not end the Whatever expression, so the .^name call becomes part of the code too, and say prints the gist of yet another WhateverCode. Assign the expression to a variable first, as on the first line, to ask what it is.

11.5 A numeric start converts the end; a string start converts nothing

When the start of a range is a real number, the end is turned into one as well: a numeric string becomes its number, and an array or list becomes its number of elements. An array or a match on the left is turned into a number too. A string on the left converts nothing, so "1"..9 is a range of strings, iterated by characters.

my @items = <a b c d e>;
say (1 .. @items).raku;
my $ten = "10";
say (1 .. $ten).raku;
my @one = 7;
say (@one .. 3).raku;
say ("1" .. 9).raku;
say ("1" .. 9).list.head(3).raku;
my $word = "abc";
try 1 .. $word;
say $!.^name;
Reference output
1..5
1..10
1..3
"1"..9
("1", "2", "3").Seq
X::Str::Numeric

@one has one element, so it becomes 1. A string that is not a number fails at once, when the range is built. An undefined right endpoint is accepted: Any stays as it is and makes an empty range, Nil becomes 0 with a warning, and True is 1.

say (1 .. Any).raku;
say (1 .. True).raku;
say (1 .. True).elems;
my $r = 1 .. Nil;
say $r.raku;
Reference output
1..Any
1..Bool::True
1
1..0
and on standard error
Use of Nil in numeric context
  in block <unit> at example.raku line 4
The editor’s engine, Raku++, prints something else here
1..Any
1..Bool::True
1
1..Nil

Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.

11.6 min and max are the endpoints as written, exclusions ignored

min, max and bounds return the stored endpoints, whether or not they are excluded and whether or not the range is empty. (^3).max is 3, which is not an element; (5..1).min is 5. The exclusions are separate: excludes-min and excludes-max. Range.new takes the two endpoints and the two exclusions as named arguments.

say (^3).min, " ", (^3).max;
say (^3).bounds.raku;
say (5..1).min, " ", (5..1).max;
say (1..*).max.^name;
say (1^..^5).excludes-min, " ", (^5).excludes-min;
say Range.new(1, 5, :excludes-min).raku;
say Range.new(*, 5).raku;
Reference output
0 3
(0, 3)
5 1
Num
True False
1^..5
-Inf..5

The end of 1..* is the Num Inf, not an Int.

11.7 is-int and infinite judge the endpoints, not the elements

is-int is True when both endpoints are Int objects. 1..5.0 and 1..1e0 hold the same elements as 1..5 and 1..1, but they are not integer ranges, and neither is 1..Inf. A string end that was converted to an Int, as in 1 .. "5", counts as one.

infinite is True when the start is -Inf, when the end is Inf, and when either endpoint is NaN. An infinity at the other end does not count: Inf..1 and 1..-Inf are empty, not infinite. is-lazy gives the same answer as infinite.

say (1..5).is-int, " ", (1..2**70).is-int;
say (1..5.0).is-int, " ", (1..1e0).is-int, " ", (1..Inf).is-int;
say (1 .. "5").is-int;
say (1..*).infinite, " ", (1..NaN).infinite;
say (Inf..1).infinite, " ", (1..-Inf).infinite;
Reference output
True True
False False False
True
True True
False False

11.8 Every range is true, even an empty oneQuirk

A defined Range is always true, whatever it contains. 1..0, 1^..1 and "b".."a" have no elements, and each of them passes an if. Only the type object Range is false.

say ?(1..0), " ", ?(1^..1), " ", ?("b".."a");
say (1..0).elems;
say "empty, but true" if 5..1;
say ?Range;
Reference output
True True True
0
empty, but true
False

Test .elems to ask whether a range has elements. The next language version changes this: under use v6.e.PREVIEW, an empty range is false.

use v6.e.PREVIEW;
say ?(1..0), " ", ?(1..5);
Reference output
False True

11.9 Two ranges are the same value when their endpoints are

A Range is a value type: two ranges with the same endpoints, of the same types, and the same exclusions are === and eqv, and unique treats them as one. A difference in the endpoints' types, or in an exclusion, makes them different even when their elements agree. A role mixed in with but changes the type, and with it the answer.

say (1..2) === (1..2);
say (1..2) eqv (1.0..2);
say (1..2) eqv (1..^2);
say ((1..3), (1..3)).unique.elems;
my $tagged = (1..2) but role { };
say (1..2) eqv $tagged;
say (1..2) == (5..6);
Reference output
True
False
False
1
False
True
The editor’s engine, Raku++, prints something else here
True
False
False
1
True
True

Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.

== is not a comparison of ranges at all: it turns each side into a number, which for a range is its number of elements, and 1..2 and 5..6 both have two.

11.10 xx and ~ bind tighter than .., and a prefix | warnsTrap

.. is looser than every arithmetic and string operator, xx included, and tighter than the comparisons (the table is in Who Takes the Operand). 1..5 xx 2 is therefore 1..(5 xx 2), a range ending in a Seq, and 1..3 ~ "x" tries to end a range at the string "3x". Both fail when the line runs.

try { my $r = 1..5 xx 2 };
say $!.message;
try { my $r = 1..3 ~ "x" };
say $!.^name;
say ((1..5) xx 2).raku;
say (1..3 X~ "a").raku;
Reference output
Seq objects are not valid endpoints for Ranges
X::Str::Numeric
(1..5, 1..5).Seq
("1a", "2a", "3a").Seq

A prefix operator binds tighter still, so |4..5 applies the slip to the 4 alone, and a one-element slip counts as 1. The compiler suspects a mistake and says so, but compiles the code; under use fatal the warning becomes a compile-time error. ~4..5 gets the same treatment, and builds the range "4"..5.

my @a = |4..5;
say @a;
Reference output
[1 2 3 4 5]
and on standard error
Potential difficulties:
    To apply a Slip flattener to a range, parenthesize the whole range.
    (Or parenthesize the whole endpoint expression, if you meant that.)
    at example.raku:1
    ------> my @a = <HERE>|4..5;
The editor’s engine, Raku++, prints something else here
(nothing on standard output; standard error says:)
===SORRY!=== Parse error at line 1: To apply a Slip flattener to a range, parenthesize the whole range.
(Or parenthesize the whole endpoint expression, if you meant that.)
      1 | my @a = |4..5;
  at example.raku:1

Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.

cmp, leg and but share the level of .. and cannot sit beside it without parentheses: (1..2) cmp 1..2 fails to compile until the second range is in parentheses too.

11.11 elems counts without iterating, and an endless range fails

An Int range counts its elements with a subtraction, however large it is. A range of other numbers counts the values it would step through, and the excluded end only removes an element that lands exactly on it: ^5.5 has six elements, 0 to 5. A range that starts after its end has none, even Inf..0. An endless range has no count, and elems returns a Failure of X::Cannot::Lazy.

say (1^..^10).elems;
say (^5.5).elems;
say (1.1..5).elems;
say (Inf..0).elems;
say (1..*).elems.^name;
try say (1..*).elems;
say $!.message;
Reference output
8
6
4
0
Failure
Cannot .elems a lazy list

11.12 An endless range is Inf as a number, though elems failsNot in the docsQuirk

A Range used as a number, with prefix +, == or <, is its number of elements, computed from the endpoints. The difference from elems shows at the edges: an endless range is Inf rather than a Failure, and an endpoint of NaN makes NaN. Inf..Inf shows how far the rule goes: it yields no elements at all, yet elems fails and the number is Inf. A range of strings counts its list. .Int of an endless range is a Failure.

say +(1..10), " ", +(1.2..4);
say +(1..*), " ", (1..*) == Inf;
say (Inf..Inf).head(5).raku;
say (Inf..Inf).elems.^name;
say +(Inf..Inf);
say +(1..NaN);
say +("aa".."ab");
say (1..*).Int.^name;
Reference output
10 3
Inf True
().Seq
Failure
Inf
NaN
2
Failure
The editor’s engine, Raku++, prints something else here
10 3
Inf True
().Seq
Int
Inf
NaN
2
Num

Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.

11.13 A numeric range steps by one from its start

The elements of a numeric range are its start, then the start plus one, and so on while they do not pass the end. The end need not be an element: 1.1..4 is 1.1, 2.1 and 3.1, and 1..4.9 stops at 4. The type of the elements follows the start, so 1..3.0 gives Ints and 1.0..3 gives Rats.

say (1.1..4).list.raku;
say (1..4.9).list.raku;
say (1.1^..^4.9).list.raku;
say (1..3.0).list.raku;
say (1.0..3).list.raku;
say (1e0..3e0).list.raku;
say (1/3..2).list.raku;
Reference output
(1.1, 2.1, 3.1)
(1, 2, 3, 4)
(2.1, 3.1, 4.1)
(1, 2, 3)
(1.0, 2.0, 3.0)
(1e0, 2e0, 3e0)
(<1/3>, <4/3>)

The third line drops the excluded start, 1.1, but keeps 4.1: an excluded end removes only a value equal to it, and no step lands on 4.9. For any other step than one, the sequence operator ... is the tool; see The Sequence Operator.

11.14 A range of single characters walks the code points

When both endpoints are strings of one character, the range holds every code point from the first to the second, punctuation included. An Int on the right of a one-character string counts as its digit. A start after the end gives an empty range.

say ('Y'..'d').Str;
say ("A".."z").elems;
say ("é".."ë").list.raku;
say ('!'^..^'&').list.raku;
say ("b".."a").list.raku;
say ("a"..*).head(3).raku;
Reference output
Y Z [ \ ] ^ _ ` a b c d
58
("é", "ê", "ë")
("\"", "#", "\$", "\%")
()
("a", "b", "c").Seq

Between Z and a lie six punctuation characters, which is why "A".."z" has 58 elements and not 52.

11.15 A range of longer strings counts each position separatelyNot in the docsQuirk

A reader who knows how succ counts strings expects "aa".."bb" to hold aa to az and then ba and bb. It does not. When the endpoints are longer than one character, Rakudo pairs the characters position by position and takes every combination of the per-position ranges, the first position changing slowest. Each position may count down as well as up.

say ("aa".."bb").list;
say ("a1".."b2").list;
say ("ab".."ba").list;
say ("08".."11").list;
say ("aa".."zz").elems;
Reference output
(aa ab ba bb)
(a1 a2 b1 b2)
(ab aa bb ba)
(08 07 06 05 04 03 02 01 18 17 16 15 14 13 12 11)
676
The editor’s engine, Raku++, prints something else here
(aa ab ac ad ae af ag ah ai aj ak al am an ao ap aq ar as at au av aw ax ay az ba bb)
(a1 a2 a3 a4 a5 a6 a7 a8 a9 b0 b1 b2)
(ab ac ad ae af ag ah ai aj ak al am an ao ap aq ar as at au av aw ax ay az ba)
(08 09 10 11)
676

Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.

"aa".."zz" has 676 elements under either reading, which is why the difference often goes unnoticed. Endpoints of different lengths give answers that follow no simple rule:

say ("a".."ad").list;
say ("1".."10").list;
say ("a".."bb").list;
say ("x".."ab").elems;
say ("a".."zz").elems;
say ("aa".."b").head(4).raku;
Reference output
(a)
(1)
(a b)
0
702
("aa",).Seq

The sequence operator gives the same answers: "aa" ... "bb" is the same four strings. Membership does not go through this list at all.

11.16 *..1 yields minus infinity foreverQuirk

An endless range is lazy. 1..* counts up forever, 1.5..* steps from 1.5, and "a"..* walks the letters and beyond. A range that starts at -Inf yields -Inf forever, because one more than minus infinity is still minus infinity, and NaN..NaN yields NaN forever. A range from Inf yields nothing. Assigning an endless range to an array keeps it lazy.

say (1.5..*).head(3).raku;
say (*..1).head(3).raku;
say (NaN..NaN).head(2).raku;
say (Inf..Inf).head(2).raku;
say (1..*)[10];
my @a = 1..*;
say @a.is-lazy, " ", @a[3];
Reference output
(1.5, 2.5, 3.5).Seq
(-Inf, -Inf, -Inf).Seq
(NaN, NaN).Seq
().Seq
11
True 4
The editor’s engine, Raku++, prints something else here
(1.5, 2.5, 3.5).Seq
(-9223372036854775808, -9223372036854775807, -9223372036854775806).Seq
(NaN, NaN).Seq
().Seq
11
True 4

Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.

A method that needs every element does not, as a rule, refuse an endless range: it starts and never returns, as .eager does. .elems, as shown above, and .reverse, just below, return a Failure instead.

11.17 reverse of a fractional range counts down from its endBug?

reverse returns a Seq. The documentation says that it holds "all elements that the Range represents", reversed. For an Int range, or a range of single characters, it is the elements in the opposite order, exclusions honoured. For any other range, Rakudo 2026.08 starts at the end point (or one below it, when the end is excluded) and counts down by one while it stays at or above the start. The result is not the reversed list, and can hold values the range never yields:

say (1^..5).reverse.raku;
say (1.1..4).list.raku;
say (1.1..4).reverse.raku;
say (1..4.5).list.raku;
say (1..4.5).reverse.raku;
say (0.5..^3).list.raku;
say (0.5..^3).reverse.raku;
Reference output
(5, 4, 3, 2).Seq
(1.1, 2.1, 3.1)
(4, 3, 2).Seq
(1, 2, 3, 4)
(4.5, 3.5, 2.5, 1.5).Seq
(0.5, 1.5, 2.5)
(2, 1).Seq
The editor’s engine, Raku++, prints something else here
(5, 4, 3, 2).Seq
(1.1, 2.1, 3.1)
(3.1, 2.1, 1.1).Seq
(1, 2, 3, 4)
(4, 3, 2, 1).Seq
(0.5, 1.5, 2.5)
(2.5, 1.5, 0.5).Seq

Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.

.list.reverse gives the elements reversed for any finite range. An endless range returns a Failure of X::Cannot::Lazy, while -Inf..3 counts down from 3 forever.

11.18 first(:end, :kv) counts its index from the endBug?

first with :end searches from the last element backwards. The documentation says that the index "is always counted from the beginning of the list", and shows :kv with :end doing so; Roast asserts the same for lists and arrays (S32-list/first-end-kv.t). On a Range, :k returns the position counted from the front, and :p pairs that position with the value. With :kv a Range in Rakudo 2026.08 returns the position counted from the end instead, so the adverbs disagree. The same call on an Array gives the position from the front.

my $r = 1..10;
say $r.first(* %% 3, :end);
say $r.first(* %% 3, :end, :k);
say $r.first(* %% 3, :end, :p).raku;
say $r.first(* %% 3, :end, :kv).raku;
my @a = 1..10;
say @a.first(* %% 3, :end, :kv).raku;
Reference output
9
8
8 => 9
(1, 9)
(8, 9)
The editor’s engine, Raku++, prints something else here
9
8
8 => 9
(8, 9)
(8, 9)

Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.

The last two results differ only in the invocant. Without :end, first on a range agrees with every other list; what it returns when nothing matches is in Nil, Any and the Undefined.

11.19 A loop over a range gets values, not containersNot in the docs

A range computes its elements; it does not store them, so there is nothing to write back to. Changing $_ in a for over a range dies, each way with a different exception, and an is rw parameter is refused. is copy gives the block its own variable. An array assigned from the range holds real containers.

try { for 1..3 { $_++ } };
say $!.^name;
try { for 1..3 { $_ = 5 } };
say $!.message;
try { for 1..3 -> $x is rw { $x++ } };
say $!.^name;
my $total = 0;
for 1..3 -> $x is copy { $x *= 10; $total += $x }
say $total;
my @a = 1..3;
$_++ for @a;
say @a;
Reference output
X::Multi::NoMatch
Cannot assign to an immutable value
X::Parameter::RW
60
[2 3 4]
The editor’s engine, Raku++, prints something else here
X::Multi::NoMatch
Cannot assign to an immutable value
Nil
60
[2 3 4]

Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.

11.20 A range in a list is one elementTrap

A Range is not flattened by the comma. for 1..3, 7..8 has two elements to iterate, the two ranges, and adding $_ to a number adds each range's count. flat or a slip opens them. Binding a range to an @ variable keeps it a Range, which cannot grow; assigning copies it into an Array.

for 1..3, 7..8 { say .raku }
my $s = 0;
$s += $_ for 1..3, 7..8;
say $s;
say (flat 1..3, 7..8).elems;
my @bound := 1..3;
say @bound.^name;
my @copied = 1..3;
say @copied.^name;
Reference output
1..3
7..8
5
5
Range
Array
The editor’s engine, Raku++, prints something else here
1..3
7..8
0
5
Array
Array

Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.

The first sum is 3 + 2: each range counts as its number of elements.

11.21 Indexing a range computes the element

$r[$i] on an Int range is worked out with an addition, so a position far into an endless range costs nothing. Past the end the answer is Nil, not Any as for an array. A fractional index is truncated and a string is read as a number. [*-1] works on a finite range and dies with X::Cannot::Lazy on an endless one, and a position that works out as negative fails with X::OutOfRange.

my $r = 1..5;
say $r[1], " ", $r[*-1];
say $r[10].raku;
say $r[2.7], " ", $r["2"];
say (1..*)[1000000];
say (1.5..4)[1];
try (^5)[*-9].Str;
say $!.^name, ": ", $!.what, " ", $!.got;
try (1..*)[*-1];
say $!.^name;
Reference output
2 5
Nil
3 3
1000001
2.5
X::OutOfRange: Effective index -4
X::Cannot::Lazy
The editor’s engine, Raku++, prints something else here
2 5
Nil
3 3
1000001
2.5
X::OutOfRange: Index -4
X::Cannot::Lazy

Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.

EXISTS-POS, the method behind :exists, is False for a negative, a fractional or a string position, although indexing accepts the last two:

my $r = 1..5;
say $r.EXISTS-POS(4), " ", $r.EXISTS-POS(5);
say $r.EXISTS-POS(2.5), " ", $r.EXISTS-POS("1");
Reference output
True False
False False
The editor’s engine, Raku++, prints something else here
True False
True True

Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.

11.22 A range subscript selects its elements, and cannot go below zero

A range inside [ ] selects the positions that the range iterates, so the exclusions count, and a range whose start is after its end selects nothing; a reversed range selects backwards. A range of fractions selects by its own elements: 1.5..3 is the positions 1.5 and 2.5, which truncate to 1 and 2. Whether the slice stops at the end of the array depends on how the range ends, a rule of the subscript rather than of the range. A range that reaches below zero dies, and in *..2 the * is -Inf, which is not a position at all.

my @l = <a b c d e>;
say @l[1.5..3];
say @l[1^..^4];
say @l[3..1].raku;
say @l[(1..3).reverse];
say @l[3..^*];
try @l[-1..1];
say $!.^name;
try @l[*..2];
say $!.^name;
Reference output
(b c)
(c d)
()
(d c b)
(d e)
X::OutOfRange
X::Numeric::CannotConvert
The editor’s engine, Raku++, prints something else here
(b c d)
(c d)
()
(d c b)
(d e)
X::OutOfRange
Nil

Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.

A hash subscript takes a range too, and looks up each element as a key: %h{1..2} asks for the keys "1" and "2". Strings take ranges with substr and comb.

11.23 A Range is immutableNot in the docs

A Range cannot be changed in place. push, pop, shift, unshift, append and prepend throw X::Immutable, whose attributes name the type and the method. splice has no candidate for a Range at all. Assigning to an element or to an attribute fails, and so does binding an element. .Array makes a copy that can change.

my $r = 1..5;
try $r.push(6);
say $!.^name, " ", $!.typename, " ", $!.method;
say $!.message;
try $r.splice(0, 1);
say $!.^name;
try $r[0] = 9;
say $!.^name;
try $r[0] := 9;
say $!.^name;
try $r.min = 2;
say $!.^name;
say $r.Array.push(6);
Reference output
X::Immutable Range push
Cannot call 'push' on an immutable 'Range'
X::Multi::NoMatch
X::Assignment::RO
X::Bind
X::Assignment::RO
[1 2 3 4 5 6]
The editor’s engine, Raku++, prints something else here
X::Immutable Range push
Cannot call 'push' on an immutable 'Range'
X::Method::NotFound
X::Assignment::RO
X::Bind
X::Assignment::RO
[1 2 3 4 5 6]

Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.

11.24 A number matches a numeric range by value, strings included

$x ~~ $range asks whether $x lies between the endpoints, not whether it is one of the elements: 2.5 is inside 1..5, and an excluded end is compared exactly, however close the value. When both endpoints are numbers, a string topic is read as a number first, so "42" and " 3 " match; a string that is not a number simply does not match. Big Ints match an open end, NaN matches nothing, and True is 1.

say 2.5 ~~ 1..5;
say 5 ~~ 1..^5;
say 4.9999999999999999999999 ~~ 0..^5;
say "42" ~~ 20..50;
say " 3 " ~~ 1..5;
say "abc" ~~ 1..10;
say 2**70 ~~ 1..*;
say NaN ~~ *..*;
say True ~~ 0..1;
Reference output
True
False
True
True
True
False
True
False
True

The third value is a Rat just below 5, so it is inside 0..^5. The same test is how grep and when use a range: (1, 2, 3, 10).grep(2..5) keeps 2 and 3. A list as the topic is a number too, its length, which surprises anyone expecting a pattern.

11.25 'raku' ~~ -∞..∞ is FalseQuirk

The documentation says that a string such as 'raku' smartmatches an infinite range, 1..* and -∞..∞ among them. In Rakudo 2026.08 it does not: both endpoints are numbers, so the string is read as a number, and a word is not one. When only one endpoint is a number, as in *.."5", the comparison is by string order instead, so 42 is below "5" but not below "3".

say "raku" ~~ 1..*;
say "raku" ~~ -∞..∞;
say 42 ~~ *..5;
say 42 ~~ *.."5";
say 42 ~~ *.."3";
say "5" ~~ *..10;
Reference output
False
False
False
True
False
True

The documentation also gives '5' ~~ *..10 as False; since both endpoints of *..10 are numbers, "5" is read as 5, and it matches.

11.26 A string range compares in string order, not by its elementsTrap

With string endpoints, membership is string order: a topic is inside when it sorts at or after the start and at or before the end. That is not the same as being an element. "abc" sorts between "a" and "b", and "ab" between "a" and "ad", although neither range holds it. A number is turned into a string, and capitals sort before small letters, as in any string comparison.

say "abc" ~~ "a".."b";
say ("a".."b").list;
say "ab" ~~ "a".."ad";
say "B" ~~ "a".."z";
say "" ~~ "a".."z";
say 42 ~~ "3".."9";
say "d" ~~ "c"..*;
Reference output
True
(a b)
True
False
False
True
True

42 ~~ "3".."9" is True because "42" sorts between "3" and "9".

11.27 One range is inside another when its endpoints are

A range on the left of ~~ matches a range on the right when its start is not before the other's start and its end not after the other's end, with the exclusions taken into account. Only the endpoints are compared. ^5 holds the same integers as 0..4, but its end, 5, is beyond 4; the empty range 5..1 is inside 1..5 because both its endpoints are. The types of the endpoints do not matter.

say 2..3 ~~ 1..12;
say 1^..5 ~~ 1..5;
say 1..5 ~~ 1^..5;
say ^5 ~~ 0..4;
say 0..4 ~~ ^5;
say 5..1 ~~ 1..5;
say 1..2 ~~ 1e0..2e0;
say 1..10 ~~ -∞..∞;
say "a".."c" ~~ 1..5;
Reference output
True
True
False
False
True
True
True
True
False

A string range never fits in a numeric one. A numeric range is compared as strings against a string range, so 1..2 ~~ "1".."2" is True.

11.28 Anything comparable can be a topic, and a Date range walks days

A range accepts any topic that can be compared with its endpoints. A junction is tested value by value. A Complex number matches when its imaginary part is zero or too small to matter. A Version is inside a Version range, and a Date inside a Date range, which also iterates day by day. A type object such as Any does not match, and warns; an object that cannot be compared at all throws X::Range::Incomparable.

say <42+0i> ~~ 10..50;
say so (3|20) ~~ 1..9;
say so (3&20) ~~ 1..9;
say v1.5 ~~ v1.0..v2.0;
my $from = Date.new("2020-01-30");
my $to = Date.new("2020-02-02");
say Date.new("2020-02-01") ~~ $from..$to;
say ($from..$to).list;
try (1..3).ACCEPTS(Mu);
say $!.message;
Reference output
True
True
False
True
True
(2020-01-30 2020-01-31 2020-02-01 2020-02-02)
Value of type 'Mu' cannot be compared with range minimum of type 'Int'
The editor’s engine, Raku++, prints something else here
True
True
False
True
False
(2020-01-30 2020-01-31 2020-02-01 2020-02-02)
Nil

Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.

Dates themselves are the subject of Dates and Times.

11.29 in-range returns True or throws

$range.in-range($value) is a check that fails loudly: True when the value is inside, an X::OutOfRange exception otherwise. An optional second argument names the value in the message, in place of the word "Value".

say (1..5).in-range(3);
say (1..5).in-range(2.5);
try (1..5).in-range(7);
say $!.^name;
say $!.message;
try ("a".."c").in-range("d", "Letter");
say $!.message;
Reference output
True
True
X::OutOfRange
Value out of range. Is: 7, should be in 1..5
Letter out of range. Is: "d", should be in "a".."c"
The editor’s engine, Raku++, prints something else here
True
True
X::OutOfRange
Value out of range. Is: 7, should be in 1..5
Value out of range. Is: d, should be in "a".."c"

Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.

11.30 Arithmetic with a number moves the endpoints, not the elementsTrap

+, -, * and / between a Range and a number do not act on each element. They build a new range from the endpoints, keeping the exclusions and the type, so a role mixed into the range survives. Multiplying a range therefore scales its endpoints and leaves the step at one, and multiplying by a negative number makes an empty range.

my $r = 1..^10;
say ($r + 1).raku;
say ($r - 1).raku;
say ($r / 2).raku;
say ($r * 2).raku;
say ($r * 2).list;
say ((1..10) * -1).raku, " ", ((1..10) * -1).elems;
my $tagged = (2..^5) but role Tagged { };
say ($tagged + 5).^name;
Reference output
2..^11
^9
0.5..^5.0
2..^20
(2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19)
-1..-10 0
Range+{Tagged}
The editor’s engine, Raku++, prints something else here
2..^11
^9
0.5..^5.0
2..^20
(2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19)
-1..-10 0
Range

Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.

$r.map(* * 2) doubles each element. Only a real number on the left of + and *, or on the right of any of the four, gets the Range treatment. Any other combination turns the range into a number, its count:

my $r = 1..10;
say 1 - $r;
say 2 / $r;
say $r + "1";
say $r + 1i;
Reference output
-9
0.2
11
10+1i
The editor’s engine, Raku++, prints something else here
1

Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.

11.31 A string range plus a number gets Failure endpointsQuirk

Arithmetic on a range works on its endpoints even when they are strings. The letters do not convert to numbers, so the new range is built from two Failures, and nothing complains until the range is used.

my $letters = "a".."c";
my $shifted = $letters + 1;
say $shifted.^name;
say $shifted.min.^name;
say $shifted.max.exception.message;
say $shifted.elems;
Reference output
Range
Failure
Cannot convert string to number: base-10 number must begin with valid digits or '.' in '<HERE>c' (indicated by <HERE>)
and on standard error
Cannot convert string to number: base-10 number must begin with valid digits or '.' in '<HERE>a' (indicated by <HERE>)
  in block <unit> at example.raku line 2

Actually thrown at:
  in block <unit> at example.raku line 6
The editor’s engine, Raku++, prints something else here
Range
Int

Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.

A Failure keeps its exception until it is used, as in any arithmetic on a string; here .elems is the first use of the start.

11.32 Ranges sort by start, then by endNot in Roast

cmp compares two ranges by their starts first, and an excluded start counts as later than an included one. When the starts are equal, it compares the ends, and an excluded end counts as earlier. A number on one side is treated as a range from that number to itself, and a list is compared with the range's elements. sort uses these rules.

say (1..2) cmp (1..3);
say (1^..2) cmp (1..2);
say (1..2) cmp 1;
say (1..2) cmp 2;
say (1..2) cmp [1, 2];
say ((1..3), (1..2), (0..5)).sort.raku;
say ((0..1), (0..^1), (0^..1)).sort.raku;
Reference output
Less
More
More
Less
Same
(0..5, 1..2, 1..3).Seq
(^1, 0..1, 0^..1).Seq

<=> and leg do not compare ranges: they turn both sides into numbers or strings first. (1..2) <=> (5..6) is Same, two elements against two, and leg compares "5 6" with "1 2 3 4 5 6 7 8 9".

say (1..2) <=> (5..6);
say (5..6) leg (1..9);
Reference output
Same
More

11.33 sum uses a formula, and answers an Int for a Num rangeNot in Roast

When a range starts at a whole number, sum does not add the elements up: it uses the formula for an arithmetic series, so the sum of a range of 10**20 numbers is immediate. A fractional end is fine, since the elements are still whole. The formula works with Ints, which gives an Int even when the endpoints are Nums or Rats, where adding the elements would not. An open end gives an infinity, and a range of letters fails.

say (1..10**20).sum;
say (0..5.5).sum;
say (1e0..3e0).sum.raku;
say (1e0..3e0).list.sum.raku;
say (1.0..3).sum.raku;
say (1..*).sum, " ", (*..5).sum, " ", (*..*).sum;
say (1.5..4).sum;
Reference output
5000000000000000000050000000000000000000
15
6
6e0
6
Inf -Inf NaN
7.5
The editor’s engine, Raku++, prints something else here
5000000000000000000050000000000000000000
15
6e0
6e0
6.0
Inf -Inf NaN
7.5

Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.

A fractional start, as in the last line, sums the list: 1.5 + 2.5 + 3.5.

11.34 int-bounds and minmax refuse what they cannot state

int-bounds returns the first and last integer that the range iterates. It needs a whole-number start, of any numeric type, and a finite end; for anything else it returns a Failure. With two variables as arguments it stores the bounds in them and returns True or False. minmax is the same list for an Int range and the two endpoints for any other, but it refuses a non-Int range with an excluded end, as there is no last element to name.

say (2..^5).int-bounds;
say (0..5.5).int-bounds;
say (5..1).int-bounds;
say (1..5).int-bounds(my $lo, my $hi), " $lo $hi";
say (1.5..3).int-bounds.exception.message;
say (1^..^5).minmax;
say (3.5..4.5).minmax;
say ("a".."z").minmax;
say (1.5..^3).minmax.exception.message;
Reference output
(2 4)
(0 5)
(5 1)
True 1 5
Cannot determine integer bounds
(2 4)
(3.5 4.5)
(a z)
Cannot return minmax on Range with excluded ends

An empty range gives its bounds in reverse order, first above last. ^Inf counts as a non-Int range, since Inf is a Num, and its minmax fails too.

11.35 max(:k) of an open range names an element that is not thereNot in the docsQuirk

min and max accept the adverbs :k, :kv and :p, to return the position of the smallest or largest element. A Range answers from its endpoints: the position is 0 for min and .end, the last position, for max, which is Inf for an endless range and -1 for an empty one. The value is the raw endpoint, excluded or not. For 2^..^6, whose elements are 3, 4 and 5, max(:kv) says position 2 holds 6. The list gives the consistent answer.

my $open = 2^..^6;
say $open.list;
say $open.max(:kv).raku;
say $open.min(:kv).raku;
say $open.list.max(:kv).raku;
say (2..Inf).max(:k);
say (1..0).max(:k);
Reference output
(3 4 5)
(2, 6)
(0, 2)
(2, 5)
Inf
-1

11.36 pick and roll choose without building the list

pick and roll without a count return one element; with a count they return a Seq. On an Int range they choose by arithmetic, so a range of 2**125 numbers is no harder than a range of ten. pick never returns more elements than the range has. An empty range gives Nil or an empty Seq, and an endless range gives Nil.

say (1..100).pick.^name;
say (1..100).pick(1).^name;
say (1..100).pick(*).elems;
say (1..100).pick(200).elems;
say ((1 +< 125) .. (1 +< 126)).pick(3).elems;
say (1..0).pick.raku, " ", (1..0).pick(3).raku;
say (1..*).pick.raku, " ", (1..*).roll.raku;
say (1.5..3.5).pick(*).sort.raku;
Reference output
Int
Seq
100
100
3
Nil ().Seq
Nil Nil
(1.5, 2.5, 3.5).Seq
The editor’s engine, Raku++, prints something else here
Int
Seq
100
100
3
Nil ()
5301748409930758661 4195207722198717274
(1.5, 2.5, 3.5).Seq

Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.

The rules for the count are those of any list, and a string count is converted. The answers at the edges are the Range's own: an empty range gives an empty Seq where an empty List gives (), and the endless range's Nil is where a lazy list returns a Failure of X::Cannot::Lazy.

11.37 After srand, the first pick from a range differsQuirk

srand seeds the draws that follow. For pick and roll on a range, as for rand, Rakudo 2026.08 replays them only from the second time: the first pick that follows an srand draws differently from every later pick after the same seed, whichever line the later call is on.

my @draws;
for ^3 {
    srand(42);
    @draws.push: (1..100).pick(3).join(",");
}
say @draws[0] eq @draws[1];
say @draws[1] eq @draws[2];
srand(42);
say (1..100).pick(3).join(",") eq @draws[2];
Reference output
False
True
True
The editor’s engine, Raku++, prints something else here
True
True
True

Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.

The general rule, with rand and roll, is in Numbers.

11.38 rand returns a real number, and refuses an empty span

.rand on a range returns a random Num between the endpoints, never equal to an excluded one. It needs two real endpoints with room between them. A range whose start is not below its end, or that has an infinite or NaN endpoint, gives a Failure of X::Range::Rand::InvalidEndpoints, and a range of strings a Failure that suggests pick.

my $x = (1..10).rand;
say $x.^name, " ", 1 <= $x <= 10;
say (1..1).rand.exception.^name;
say (1..1).rand.exception.message;
say (1..Inf).rand.exception.^name;
say ("a".."z").rand.exception.message;
Reference output
Num True
X::Range::Rand::InvalidEndpoints
Impossible to generate random numbers for a range where endpoints are equal
X::Range::Rand::InvalidEndpoints
Can only get a random value on Real values, did you mean .pick?

11.39 A Range converts to lists and hashes; its Capture has six names

.list is a List, and .flat and .Seq are Seqs; for an endless range all of them, and .Array, stay lazy. keys, kv and pairs number the elements from 0. .Map and .Hash pair up the elements, and an odd count throws X::Hash::Store::OddNumber.

my $r = 1..4;
say $r.list.^name, " ", $r.Seq.^name, " ", $r.Array.^name;
say $r.kv;
say $r.antipairs;
say $r.Hash.raku;
try (1..3).Hash;
say $!.^name;
say (1..*).Array.is-lazy;
Reference output
List Seq Array
(0 1 1 2 2 3 3 4)
(1 => 0 2 => 1 3 => 2 4 => 3)
{"1" => 2, "3" => 4}
X::Hash::Store::OddNumber
True
The editor’s engine, Raku++, prints something else here
List Seq Array
(0 1 1 2 2 3 3 4)
(1 => 0 2 => 1 3 => 2 4 => 3)

Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.

.Capture holds no elements at all: it is six named arguments describing the range. A sub-signature can unpack a range through it, but it must accept the names it does not want, with *%:

say (1..^3).Capture.raku;
sub span((:$min, :$max, *%)) { "$min to $max" }
say span(3..7);
sub strict((:$min, :$max)) { "$min to $max" }
try strict(3..7);
say $!.^name;
Reference output
\(:excludes-max, :!excludes-min, :!infinite, :is-int, :max(3), :min(1))
3 to 7
X::AdHoc
The editor’s engine, Raku++, prints something else here
\(:excludes-max(Bool::True), :excludes-min(Bool::False), :is-int(Bool::True), :max(3), :min(1))
3 to 7
Nil

Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.

11.40 String methods see the range's textQuirkNot in the docs

A Range is a Cool, so the string methods work on it, and they work on its .Str: the elements joined by spaces. .chars of 1..3 is 5 and .flip reverses the text. Numeric methods see its count. succ and pred do not exist for a range. contains and index answer from the text but first warn that they do, since a search among the elements was more likely meant.

my $r = 1..3;
say $r.chars;
say $r.flip;
say $r.sqrt;
say $r ~ "!";
try $r.succ;
say $!.^name;
say (10..12).contains("0 1");
Reference output
5
3 2 1
1.7320508075688772
1 2 3!
X::Method::NotFound
True
and on standard error
Applying '.contains' to a Range will look at its .Str representation.
Did you mean 'needle (elem) Range'?
  in block <unit> at example.raku line 8
The editor’s engine, Raku++, prints something else here
5
3 2 1
0
1 2 3!
Nil
True

Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.

"0 1" is found across the space between 10 and 11. Lists behave the same way, with a warning of their own.

11.41 Each numeric type has a Range, and a native type its exact bounds

.Range on a numeric type returns the values it can hold. Int gives -Inf^..^Inf and UInt 0..^Inf; Num and Rat include the infinities. The native integer types give their exact limits. Being ranges, they test magnitude only: 1.5 is inside Int.Range. Str has no .Range, and an instance such as 42 is refused.

say Int.Range.raku, " ", UInt.Range.raku;
say Num.Range.raku;
say int8.Range.raku, " ", byte.Range.raku;
say uint64.Range.raku;
say 200 ~~ int8.Range, " ", 2**70 ~~ UInt.Range;
say 1.5 ~~ Int.Range;
say Inf ~~ Int.Range, " ", Inf ~~ Num.Range;
try 42.Range;
say $!.^name;
Reference output
-Inf^..^Inf 0..^Inf
-Inf..Inf
-128..127 0..255
0..18446744073709551615
False True
True
False True
X::Parameter::InvalidConcreteness
The editor’s engine, Raku++, prints something else here
-Inf^..^Inf 0..^Inf
-Inf..Inf
-128..127 0..255
0..18446744073709551615
False True
True
False True
X::Method::NotFound

Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.

11.42 The Range type object is a list of one undefined elementNot in the docsNot in Roast

Range itself behaves like any type object in a list: one element, itself. It is false and undefined, and as a number it is 0 with a warning. The methods that need endpoints refuse it. 5 ~~ Range is a type check, and a Range is a Positional and an Iterable but not a List.

say Range.elems;
say Range.list.raku;
say ?Range, " ", Range.defined;
try Range.min;
say $!.^name;
say 5 ~~ Range, " ", (1..2) ~~ Range;
say Range.^mro.map(*.^name);
say (1..2) ~~ Positional, " ", (1..2) ~~ List;
say +Range;
Reference output
1
(Range,)
False False
X::AdHoc
False True
(Range Cool Any Mu)
True False
0
and on standard error
Use of uninitialized value of type Range in numeric context
  in block <unit> at example.raku line 9
The editor’s engine, Raku++, prints something else here
1
(Range,)
False False
X::Multi::NoMatch
False True
(Any Mu)
True False
0

Measured with Raku++ 4.0.1-245-ge1e6e3a1-modified (2026-09-28) arm64-darwin. The book shows the reference compiler’s output; the editor runs Raku++ compiled to WebAssembly.