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