Regular Expressions
|
This section documents the Swift 6 language mode as shipped by Swift 6.3, as published in The Swift Programming Language at docs.swift.org, which is the reference these pages are written and verified against. 6.4-beta-only features are always flagged as such — never presented as baseline. This content was generated with the assistance of AI and should be verified against docs.swift.org before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Swift 5.7 added first-class regular expression support to the language and standard library: regex literals
checked by the compiler, a Regex<Output> type whose captures are statically typed, and a result-builder DSL
(RegexBuilder) for composing a regex out of named, reusable pieces instead of a single opaque pattern string — see Pattern Matching for how a Regex also participates
in switch/if case pattern matching via ~=.
Regex Syntax Refresher
A regex describes a set of strings by combining: literal characters (matched as-is); metacharacters with
special meaning (. any character, ^/$ start/end anchors); character classes ([abc], [a-z], \d
digits, \w word characters, \s whitespace, each negatable as [^…]/\D/\W/\S); quantifiers
( zero-or-more, + one-or-more, ? zero-or-one, {n,m} a repetition range), each greedy by default and
made lazy with a trailing ? (.?); groups (…) for capturing a sub-match, (?:…) for grouping
without capturing, and (?<name>…) for a named capture; and assertions like (?=…)/(?!…) (lookahead)
and (?⇐…)/(?<!…) (lookbehind) that match a position without consuming characters.
Regex Literals and the Regex Type
let simplePattern = /\d{3}-\d{4}/ // a regex literal -- checked by the compiler like any expression
let withSlashes = #/https://example\.com/(?<path>.+)/# // extended literal: #/ ... /# allows literal `/` and `#` inside
let dynamicPattern = "\\d+"
let runtimeRegex = try Regex(dynamicPattern) // Regex<AnyRegexOutput> -- built from a string at run time
A regex literal (/pattern/) is checked for syntax errors at compile time and typed as Regex<Output>, where
Output is inferred from the literal’s capture groups — exactly analogous to how a string literal is checked
and typed as String. The extended delimiter form (/pattern/, with as many leading as needed) lets the
pattern itself contain an unescaped / or , useful for patterns like a URL matcher. Building a Regex from a
String at run time (try Regex(pattern)) is necessary whenever the pattern isn’t known until run time (read
from a config file, constructed from user input); it can throw if the string isn’t a valid pattern, and its
captures are erased to AnyRegexOutput since the compiler cannot inspect a run-time string’s groups.
Matching, Replacing and Splitting
let text = "Order #1234 shipped on 2026-09-01, order #5678 shipped on 2026-09-03"
let orderPattern = /order #(?<id>\d+) shipped on (?<date>\d{4}-\d{2}-\d{2})/.ignoresCase()
if let match = text.firstMatch(of: orderPattern) {
match.id // "1234" -- named captures are accessible as properties on the match's output tuple
match.date // "2026-09-01"
}
for match in text.matches(of: orderPattern) {
print("\(match.id) on \(match.date)")
}
let onlyDigits = "12345"
if let whole = try? Regex(#"\d+"#).wholeMatch(in: onlyDigits) {
print(whole.output) // matches only if the ENTIRE string matches, unlike firstMatch
}
"hello world".contains(/wor.d/) // true
let redacted = text.replacing(/\d{4}/, with: "****") // replaces every 4-digit run
let fields = "a,b,,c".split(separator: /,/, omittingEmptySubsequences: false) // ["a", "b", "", "c"]
firstMatch(of:) finds the first (leftmost) match anywhere in the string and returns a Regex<Output>.Match
whose .output (or named-capture properties, as with match.id/match.date above) exposes the captures with
their inferred static types; matches(of:) returns every non-overlapping match as a sequence; wholeMatch(in:)
succeeds only if the pattern matches the entire input, not just a substring of it. contains(:) is a Bool
shortcut for "does a match exist anywhere," and replacing(:with:)/split(separator:) are regex-aware
overloads of the same-named String/Collection operations already covered in
Strings and Characters and
Collections.
Typed Captures
let logLine = "2026-09-01 14:32:07 ERROR disk full"
let logPattern = /(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) (\w+) (.+)/
if let match = logLine.firstMatch(of: logPattern) {
let (_, date, time, level, message) = match.output // (Substring, Substring, Substring, Substring, Substring)
print("\(level): \(message) at \(date) \(time)")
}
Every unnamed capture group in a regex literal widens the match’s .output tuple by one Substring element (the
first element is always the whole match itself), fully typed at compile time — there is no need to index into
an untyped array of strings the way most regex APIs require, and mismatched group counts or types are caught
before the code ever runs.
The RegexBuilder DSL
import RegexBuilder
let orderRegex = Regex {
"order #"
Capture {
OneOrMore(.digit)
} transform: { Int($0)! } // transform lets a capture come out as Int instead of Substring
" shipped on "
TryCapture {
Repeat(count: 4) { .digit }
"-"
Repeat(count: 2) { .digit }
"-"
Repeat(count: 2) { .digit }
} transform: { try? Date($0, strategy: .iso8601.year().month().day()) }
}
.ignoresCase()
let choice = Regex {
ChoiceOf {
"cat"
"dog"
"bird"
}
}
RegexBuilder (imported separately from the standard library) builds a Regex out of composable pieces using
the same result-builder mechanism as
Result Builders: Capture/TryCapture mark a
sub-pattern as a typed capture (optionally with a transform closure converting the matched Substring into
another type, Int/Date/anything else, and failing the whole match if TryCapture’s transform returns `nil);
One/OneOrMore/ZeroOrMore/Repeat express quantifiers without regex-string counting; and ChoiceOf
expresses alternation. The payoff mirrors the one in
Result Builders: a regex assembled this way is easier to
read, comment, and reuse piece by piece than a single dense pattern string, while still compiling down to the
same Regex<Output> engine.
See Also
-
Pattern Matching — how
Regexparticipates inswitch/if casevia~=. -
Strings and Characters — the
String/Substringtypes regex matches and captures are expressed in terms of. -
Lexical Structure and Style — the regex literal’s own lexical grammar (
/…/and/…/). -
Result Builders — the general mechanism
RegexBuilderis itself built on.
References
TSPL: Lexical Structure → Regular Expression Literals; stdlib Regex; RegexBuilder.