raku.gg / regex

Named Captures

2026-03-22

Raku's regex engine goes far beyond what you find in most languages. Named captures let you label parts of your match, producing structured data instead of opaque numbered groups. Once you start using them, you will never want to go back to $1, $2, $3.

Basic Named Captures

Use $<name>=[ ... ] to create a named capture:
my $str = "2026-03-22"; if $str ~~ / $<year>;=[\d ** 4] '-' $<month>;=[\d ** 2] '-' $<day>;=[\d ** 2] / { say $<year>;; # 2026 say $<month>;; # 03 say $<day>;; # 22 }
Named captures are accessed through the match object using $<name> syntax (or $/<name>).

The Match Object

Every successful match produces a Match object. Named captures become keys in this object:
my $m = "Alice: 42" ~~ / $<name>;=[\w+] ': ' $<age>;=[\d+] /; say $m; # Full match: "Alice: 42" say $m<;name>;; # "Alice" say $m<;age>;; # "42" say $m<;name>;.Str; # "Alice" as a plain string say $m<;age>;.Int; # 42 as an integer say $m.hash.keys; # (name age)
Match objects are nested structures. Each named capture is itself a Match object with .from, .to, .Str, and other useful methods.

Captures from Named Rules

When you call a named regex or token, the name automatically becomes a capture:
my regex year { \d ** 4 } my regex month { \d ** 2 } my regex day { \d ** 2 } if "2026-03-22" ~~ / <;year>; '-' <;month>; '-' <;day>; / { say $<year>;; # 2026 say $<month>;; # 03 say $<day>;; # 22 }
This is how grammars work under the hood. Each rule call creates a named capture in the match tree.

Nested Captures

Named captures can be nested, creating a tree structure:
my $addr = "192.168.1.100:8080"; if $addr ~~ / $<ip>;=[ (\d+) ** 4 % '.' ] ':' $<port>;=[\d+] / { say $<ip>;; # 192.168.1.100 say $<port>;; # 8080 say $<ip>;[0]; # First octet: 192 say $<ip>;[1]; # Second octet: 168 }
The numbered captures inside a named capture are accessible as children.

Repeated Named Captures

When a named capture repeats, you get an array of matches:
my $csv = "Alice,Bob,Carol,Dave"; if $csv ~~ / $<name>;=[\w+]+ % ',' / { for $<name>;.list -> $n { say "Found: $n"; } }
Output:
Found: Alice Found: Bob Found: Carol Found: Dave

Aliasing Captures

You can alias one capture name to another pattern:
# Give a meaningful name to a complex pattern my $log = "2026-03-22T14:30:00 ERROR disk full"; if $log ~~ / $<timestamp>;=[\S+] \s+ $<level>;=[\S+] \s+ $<message>;=[\N+] / { say "Time: $<timestamp>"; say "Level: $<level>"; say "Msg: $<message>"; }

Captures with Quantifiers

Named captures interact naturally with quantifiers:
my $text = "scores: 10 25 30 45 50"; if $text ~~ / 'scores:' \s+ $<score>;=[\d+]+ % \s+ / { my @scores = $<score>;.list>;>.Int; say "Scores: @scores[]"; say "Average: {([+] @scores) / @scores.elems}"; }

Accessing Captures Programmatically

You can iterate over all captures in a match:
my $m = "name=Alice age=30 city=Toronto" ~~ / [ $<key>;=[\w+] '=' $<value>;=[\w+] ]+ % \s+ /; for $<key>;.list Z $<value>;.list -> ($k, $v) { say "$k => $v"; }
Output:
name => Alice age => 30 city => Toronto

Non-capturing Groups

Sometimes you need grouping without capturing. Use [ ... ] (square brackets) for non-capturing groups:
# [ ... ] groups without capturing if "hello" ~~ / [he|she] 'llo' / { say "Matched: $/"; # No extra captures } # ( ... ) groups WITH capturing (positional) if "hello" ~~ / (he|she) 'llo' / { say "Captured: $/[0]"; }

Practical Example: Log Parser

Here is a complete log line parser using named captures:
my @logs = ( '2026-03-22 14:30:00 [INFO] Server started on port 8080', '2026-03-22 14:30:05 [WARN] High memory usage: 89%', '2026-03-22 14:30:10 [ERROR] Connection refused: db-host', ); for @logs -> $line { if $line ~~ / $<date>;=[\d+\-\d+\-\d+] \s+ $<time>;=[\d+\:\d+\:\d+] \s+ '[' $<level>;=[\w+] ']' \s+ $<message>;=[\N+] / { say "{$<level>}: {$<message>}"; } }

Tips

Named captures make Raku's regex engine feel more like a parsing framework than a string matching tool. In the next regex post, we will look at subrules for building reusable regex components.