Module regex/match
Match result type — represents the result of a regex match, including the matched text, position, and captured groups.
Every offset a RegexMatch reports is a BYTE offset into the input, on a
UTF-8 boundary (D4, 2026-08-26 — they used to be rune indices), so they can
be fed straight into String.substring / index_of. See
docs/en-US/STRINGS.md.
A match is EAGER and self-contained: it holds the matched text, the whole
input, every group's text and every group's span, all captured when the VM
reported success. That is why there is no separate captures method the way
Rust splits Regex::find from Regex::captures — Yo's find already pays
for the groups, so a second entry point would buy nothing. It also means the
match keeps the input string alive for as long as it lives.
Group numbering is 1-based, with group 0 meaning the whole match, matching
the $1/\1 vocabulary of the patterns themselves.
Stability
unstable — it is one of the four public std/regex names and its accessors
are settled, but two shapes are still open. group(i) and named_group(n)
return .None both for "that group exists and did not participate" and for
"there is no such group", so a caller cannot tell a typo'd group name from
an optional group that did not match; Rust's Captures::name has the same
conflation but its len() lets you rule the second out, and
group_count() here counts only the groups the pattern declared.
named_group is also a LINEAR scan of the name table on every call, which
is fine for a handful of names and wrong for a pattern with dozens.
Freezing waits on deciding whether the two failure modes separate.
Types
A single regex match result.
Fields
| Name | Type | Description |
|---|---|---|
_value | String | |
_index | usize | |
_end | usize | |
_input | String | |
_groups | ArrayList(Option(String)) | |
_group_spans | ArrayList(Option(Range(usize))) | |
_group_names | ArrayList(GroupNameEntry) |
impl(RegexMatch, ...)
new : (RegexMatch) fn(value : String, index : usize, end : usize, input : String, groups : ArrayList(Option(String)), group_spans : ArrayList(Option(Range(usize))), group_names : ArrayList(GroupNameEntry)) -> RegexMatchAssemble a match. Called only by the engine (Regex.find and the
iterators) after the VM reports success — every argument is a slot the
VM already computed, so nothing here validates or re-derives anything.
Parameters
| Name | Type | Notes | Description |
|---|---|---|---|
value | String | The matched text itself — the same bytes as
| |
index | usize | Start position of the match as a byte offset into the input. Basis change (D4, 2026-08-26): this used to be a character
(rune) index; it is now a byte index, matching | |
end | usize | End position of the match as a byte offset into the input
(exclusive — the offset of the first byte AFTER the match). Always
on a UTF-8 character boundary, for the same reason as | |
input | String | The whole string the match was found in, not just the matched part. The
match holds a reference to it, so the offsets | |
groups | ArrayList(Option(String)) | ||
group_spans | ArrayList(Option(Range(usize))) | ||
group_names | ArrayList(GroupNameEntry) |
Returns: RegexMatch
value : (RegexMatch) fn(self : RegexMatch) -> StringThe matched text itself — the same bytes as
input().substring(index(), end()), captured at match time.
Parameters
| Name | Type | Notes |
|---|---|---|
self | RegexMatch |
Returns: String
index : (RegexMatch) fn(self : RegexMatch) -> usizeStart position of the match as a byte offset into the input.
Basis change (D4, 2026-08-26): this used to be a character
(rune) index; it is now a byte index, matching String's
byte-indexed API. The offset is always on a UTF-8 character
boundary (a valid pattern cannot match at a continuation byte), so
it can be fed directly to String.substring / index_of on the
input. Use input().substring(usize(0), m.index()).chars().count() if
a rune count is really needed.
Parameters
| Name | Type | Notes |
|---|---|---|
self | RegexMatch |
Returns: usize
end : (RegexMatch) fn(self : RegexMatch) -> usizeEnd position of the match as a byte offset into the input
(exclusive — the offset of the first byte AFTER the match). Always
on a UTF-8 character boundary, for the same reason as index().
span() is index() .. end().
Parameters
| Name | Type | Notes |
|---|---|---|
self | RegexMatch |
Returns: usize
span : (RegexMatch) fn(self : RegexMatch) -> Range(usize)The whole match as a byte range index() .. end() into the input.
Parameters
| Name | Type | Notes |
|---|---|---|
self | RegexMatch |
Returns: Range(usize)
input : (RegexMatch) fn(self : RegexMatch) -> StringThe whole string the match was found in, not just the matched part. The
match holds a reference to it, so the offsets index() / end() /
span() / group_span() stay meaningful for as long as the match does.
Parameters
| Name | Type | Notes |
|---|---|---|
self | RegexMatch |
Returns: String
group : (RegexMatch) fn(self : RegexMatch, idx : usize) -> Option(String)The text of capture group idx, 1-based, with 0 meaning the whole
match — the same numbering $1 and \1 use in patterns.
.None means EITHER that the group did not participate in this match (an
unmatched (x)?, or an alternative that was not taken) OR that idx is
past group_count(). The two are not distinguishable here; compare idx
against group_count() first if that matters.
Parameters
| Name | Type | Notes |
|---|---|---|
self | RegexMatch | |
idx | usize |
group_span : (RegexMatch) fn(self : RegexMatch, idx : usize) -> Option(Range(usize))Capture group's byte range into the input (1-based, mirroring
group(); group 0 is the full match's span()), or .None when
the group did not participate in the match or the index is out of
range. A participating group's range satisfies
input().substring(r.start, r.end) == group(idx).unwrap().
Parameters
| Name | Type | Notes |
|---|---|---|
self | RegexMatch | |
idx | usize |
named_group : (RegexMatch) fn(self : RegexMatch, name : String) -> Option(String)The text of the group declared as (?<name>...), or .None when no
group has that name OR the named group did not participate — the same
conflation as group.
O(number of named groups): the name table is scanned linearly on every call, so a loop over many names over a long input is quadratic. Names are compared for exact equality, case-sensitively.
Parameters
| Name | Type | Notes |
|---|---|---|
self | RegexMatch | |
name | String |
group_count : (RegexMatch) fn(self : RegexMatch) -> usizeHow many capture groups the PATTERN declared, not counting group 0 and
not counting how many actually participated. So a non-participating group
is still counted, and group(i) is a legal call for every
1 <= i <= group_count().
Parameters
| Name | Type | Notes |
|---|---|---|
self | RegexMatch |
Returns: usize