# Swift Regular Expressions: Regex Literals and RegexBuilder

## Pro Summary

Swift Regex (Swift 5.7+) is typed. A literal such as `/(?<year>\d{4})-(?<month>\d{2})/` is validated at compile time and typed as `Regex<(Substring, year: Substring, month: Substring)>`, so a malformed pattern is a build error. In RegexBuilder, `TryCapture` converts a capture to `Int` or another type inline. Matching is Unicode-correct by default: `.` matches a whole grapheme cluster and `\d`/`\w` accept non-ASCII digits and letters, which suits text processing but not strict ASCII validation. The engine ships with the OS (macOS 13, iOS 16), and `Regex` isn't `Sendable`, which matters when you cache patterns under Swift 6.

```
import RegexBuilder

let literal = /(?<year>\d{4})-(?<month>\d{2})/   // Regex<(Substring, year: Substring, month: Substring)>

let builder = Regex {
    "ID-"
    TryCapture { Repeat(count: 6) { .digit } } transform: { Int($0) }
}                                                  // Regex<(Substring, Int)>

if let m = "ID-000042".wholeMatch(of: builder) { print(m.1) }   // 42
```

Requires: Swift 5.7+ (SE-0350, SE-0351, SE-0354). `Regex` requires macOS 13, iOS 16, tvOS 16, or watchOS 9 at runtime. Bare `/.../` literals are on by default in the Swift 6 language mode. Swift 5 mode needs the `BareSlashRegexLiterals` upcoming feature, or the always-available `#/.../#` form.

Gotchas:

- `String.firstMatch(of:)`, `wholeMatch(of:)`, and `contains(_:)` don't throw, so wrapping them in `try?` only produces a warning. `Regex.firstMatch(in:)` and `wholeMatch(in:)` do throw and need `try`.
- Matching works on grapheme clusters by default: `/./` matches all of `"e\u{301}"` and `"🇺🇸"`. Use `.matchingSemantics(.unicodeScalar)` for scalar-level matching.
- A pattern built at runtime with `try Regex(string)` returns `Regex<AnyRegexOutput>` with untyped captures. Pass `as: (Substring, Substring, Substring).self` to get a typed tuple; a capture-count mismatch throws at runtime.
- In Swift 5 language mode without the upcoming feature, `/\d+/` fails to parse with "'/' is not a prefix unary operator". Packages with a `swift-tools-version` below 6.0, or with `swiftLanguageModes: [.v5]`, compile in Swift 5 mode and hit this.
- Numbered captures (`match.1`) shift whenever you add a group. Named captures (`(?<year>...)` read as `match.year`) survive refactoring.

Primary sources:

- [SE-0350: Regex Type and Overview](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0350-regex-type-overview.md)
- [SE-0351: Regex builder DSL](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0351-regex-builder.md)
- [SE-0354: Regex Literals](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0354-regex-literals.md)
- [Apple Developer Documentation: Regex](https://developer.apple.com/documentation/swift/regex)

Source: https://devdocs10.com/articles/swift/swift-regular-expressions  
By Murat Bo · Updated 2026-10-17 · DevDocs10 (https://devdocs10.com)

Swift includes modern, type-safe support for regular expressions, so you can search text, validate input, extract values, and transform strings without relying only on older Foundation APIs. In this article, you will learn what Swift regular expressions are, how regex literals and `RegexBuilder` work, when to use each approach, and how to avoid the mistakes that commonly confuse beginners.

> **Quick answer:** In Swift 5.7+ (macOS 13 / iOS 16 at runtime), you can write regular expressions either as regex literals such as `/\d+/` or by building them with `RegexBuilder`. Use literals when the pattern is short and familiar, and use `RegexBuilder` when you want clearer, more structured, and more type-safe patterns.

> **Difficulty:** Intermediate
>
> **Helpful to know first:** You will understand this better if you already know basic Swift syntax, how `String` values work, and how functions and conditionals are used in simple programs.

## 1. What Is Swift Regular Expressions?

A regular expression, usually called a regex, is a pattern used to find, validate, or extract text. Swift’s modern regex system lets you describe text patterns directly in Swift code and work with matches in a safer way than many older regex APIs.

- A regex describes a text pattern rather than a fixed string.
- You can use regex to check whether text matches a format, such as an email-like value or an order number.
- You can extract parts of text, such as IDs, dates, or filenames.
- Swift supports both regex literals and the builder-style `RegexBuilder` API.
- Swift regex is often easier to integrate with the language than older `NSRegularExpression`-based code.

For example, a simple regex can find one or more digits inside a string. More advanced patterns can capture multiple parts, such as a username and domain from an email-like value.

> Swift regex support is powerful, but it still helps to think carefully about what text you want to match. A regex that is too broad can accept invalid input, while one that is too strict can reject valid data.

## 2. Why Swift Regular Expressions Matters

Text processing appears in many real apps. User input, log files, imported CSV data, API responses, filenames, and command-line arguments often contain structured text that needs to be checked or parsed.

You would use Swift regular expressions when:

- You need to validate a format such as a postal code, tag, code, or identifier.
- You want to find values embedded in larger text.
- You need to capture specific pieces from a string, such as year, month, and day.
- You want more flexible matching than `contains`, `hasPrefix`, or `hasSuffix` can provide.

You would not automatically use regex for every string task. If a simple string method expresses the intent more clearly, that is often the better choice. For example, checking whether a file ends with `.jpg` is usually clearer with `hasSuffix` than with a regex.

## 3. Basic Syntax

Swift gives you two main ways to create a regex: a regex literal and `RegexBuilder`.

### Using a regex literal

A regex literal is written between slashes. This example looks for one or more digits.

```swift
let text = "Order 4829 shipped"
let regex = /\d+/

if let match = text.firstMatch(of: regex) {
    print(match.0)
}
```

Output (tested with Swift 6.4):

```text
4829
```

Here is what each part means:

- `/\d+/` means one or more digits.
- `firstMatch(of:)` searches the string for the first matching part.
- `match.0` refers to the full text that matched.

This prints the first number found in the string.

### Using RegexBuilder

`RegexBuilder` lets you describe the same pattern using readable Swift components.

```swift
import RegexBuilder

let text = "Order 4829 shipped"

let regex = Regex {
    OneOrMore {
        CharacterClass.digit
    }
}

if let match = text.firstMatch(of: regex) {
    print(match.0)
}
```

Output (tested with Swift 6.4):

```text
4829
```

This version matches the same digits, but it is often easier to read when patterns become longer or more structured.

### Whole match vs first match

A common point of confusion is the difference between matching part of a string and matching the entire string.

```swift
let code = "AB-123"
let regex = /[A-Z]{2}-\d{3}/

let partial = code.firstMatch(of: regex)
let whole = code.wholeMatch(of: regex)
```

`firstMatch(of:)` looks for a matching part somewhere in the string. `wholeMatch(of:)` requires the entire string to fit the pattern.

## 4. Step-by-Step Examples

### Example 1: Find the first number in a sentence

This is one of the simplest practical uses of regex. The pattern below finds a sequence of digits.

```swift
let message = "Your table number is 27."
let numberRegex = /\d+/

if let match = message.firstMatch(of: numberRegex) {
    print("Found number:", match.0)
}
```

Output (tested with Swift 6.4):

```text
Found number: 27
```

This prints the first digit sequence found in the sentence. It is useful for basic extraction tasks from mixed text.

### Example 2: Validate a simple product code

When you want the entire input to match a pattern, use `wholeMatch(of:)`. This example expects exactly two uppercase letters, a dash, and three digits.

```swift
let productCode = "TV-204"
let productRegex = /[A-Z]{2}-\d{3}/

if productCode.wholeMatch(of: productRegex) != nil {
    print("Valid product code")
} else {
    print("Invalid product code")
}
```

Output (tested with Swift 6.4):

```text
Valid product code
```

This is better than a partial search because the whole input must match the format, not just one part of it.

### Example 3: Capture parts of a date

Regex becomes especially useful when you want to extract multiple values from one string.

```swift
let dateText = "2025-08-17"
let dateRegex = /(\d{4})-(\d{2})-(\d{2})/

if let match = dateText.wholeMatch(of: dateRegex) {
    let year = match.1
    let month = match.2
    let day = match.3

    print("Year:", year)
    print("Month:", month)
    print("Day:", day)
}
```

Output (tested with Swift 6.4):

```text
Year: 2025
Month: 08
Day: 17
```

The full match is stored at `match.0`, and captured groups begin at `match.1`. This is a common way to pull structured values from formatted text.

### Example 4: Build the same date pattern with RegexBuilder

For complex patterns, `RegexBuilder` can be easier to read and maintain.

```swift
import RegexBuilder

let dateText = "2025-08-17"

let dateRegex = Regex {
    Capture {
        Repeat(count: 4) {
            CharacterClass.digit
        }
    }
    "-"
    Capture {
        Repeat(count: 2) {
            CharacterClass.digit
        }
    }
    "-"
    Capture {
        Repeat(count: 2) {
            CharacterClass.digit
        }
    }
}

if let match = dateText.wholeMatch(of: dateRegex) {
    print(match.1, match.2, match.3)
}
```

Output (tested with Swift 6.4):

```text
2025 08 17
```

This example shows the same idea as the literal version, but with a builder structure that many developers find easier to edit later.

## 5. Practical Use Cases

- Checking whether a user-entered code matches a required format before submitting a form.
- Extracting order numbers, invoice IDs, or ticket references from messages.
- Parsing simple text-based data formats when full JSON or XML parsing is unnecessary.
- Finding hashtags, mentions, or tagged values inside text content.
- Cleaning imported data by detecting extra spaces, separators, or formatting mistakes.
- Scanning logs for timestamps, error codes, or repeated patterns.

## 6. Common Mistakes

### Mistake 1: Using `firstMatch` when the entire string must be valid

Beginners often search for a match anywhere in the string when they actually want to validate the whole input.

**Problem:** This code accepts invalid input because it only checks whether some part of the string matches the pattern.

```swift
let input = "Code: AB-123 extra"
let regex = /[A-Z]{2}-\d{3}/

if input.firstMatch(of: regex) != nil {
    print("Accepted")
}
```

Output (tested with Swift 6.4):

```text
Accepted
```

**Fix:** Use `wholeMatch(of:)` when the entire input must follow the expected format.

```swift
let input = "AB-123"
let regex = /[A-Z]{2}-\d{3}/

if input.wholeMatch(of: regex) != nil {
    print("Accepted")
}
```

Output (tested with Swift 6.4):

```text
Accepted
```

The corrected version works because the whole string must match the regex, not just one substring.

### Mistake 2: Forgetting that regex is not always the clearest tool

Some tasks are so simple that a regular expression makes the code harder to read than necessary.

**Problem:** This code uses a regex for a basic suffix check, which adds complexity without any real benefit.

```swift
let filename = "photo.jpg"
let regex = /.*\.jpg/

if filename.wholeMatch(of: regex) != nil {
    print("JPEG file")
}
```

Output (tested with Swift 6.4):

```text
JPEG file
```

**Fix:** Use direct string APIs when they express the intent more clearly.

```swift
let filename = "photo.jpg"

if filename.hasSuffix(".jpg") {
    print("JPEG file")
}
```

Output (tested with Swift 6.4):

```text
JPEG file
```

The corrected version works because `hasSuffix` is simpler, clearer, and designed for this exact check.

### Mistake 3: Assuming a regex match always exists

Match methods return an optional result because the pattern may not be found.

**Problem:** This code force-unwraps a match that may be `nil`, which can crash at runtime if the text does not match.

```swift
let text = "No number here"
let regex = /\d+/

let match = text.firstMatch(of: regex)!
print(match.0)
```

**Fix:** Safely unwrap the optional result before using the match.

```swift
let text = "No number here"
let regex = /\d+/

if let match = text.firstMatch(of: regex) {
    print(match.0)
} else {
    print("No match found")
}
```

Output (tested with Swift 6.4):

```text
No match found
```

The corrected version works because it handles the case where the regex does not match the text.

### Mistake 4: Choosing an unreadable literal when RegexBuilder would be clearer

Regex literals are concise, but complex patterns can become difficult to understand and maintain.

**Problem:** This pattern is compact, but it is harder to review and update because the structure is hidden inside punctuation-heavy syntax.

```swift
let regex = /(\d{4})-(\d{2})-(\d{2})/
```

**Fix:** Use `RegexBuilder` when readability and maintenance matter more than compact syntax.

```swift
import RegexBuilder

let regex = Regex {
    Capture {
        Repeat(count: 4) { CharacterClass.digit }
    }
    "-"
    Capture {
        Repeat(count: 2) { CharacterClass.digit }
    }
    "-"
    Capture {
        Repeat(count: 2) { CharacterClass.digit }
    }
}
```

The corrected version works because the pattern structure is easier to see and modify.

## 7. Best Practices

### Practice 1: Start with the simplest string tool that solves the problem

Regex is powerful, but clarity matters. Prefer plain string APIs for straightforward tasks, and switch to regex when the pattern truly needs it.

```swift
// Less preferred for a simple prefix check
let regex = /ERROR:.*/

// Preferred
let line = "ERROR: Disk full"
if line.hasPrefix("ERROR:") {
    print("Error line")
}
```

Output (tested with Swift 6.4):

```text
Error line
```

This keeps simple code readable and reserves regex for cases where pattern matching is genuinely useful.

### Practice 2: Use `wholeMatch` for validation rules

When checking a required format, make your intent explicit by requiring the entire string to match.

```swift
let zipCode = "12345"
let zipRegex = /\d{5}/

if zipCode.wholeMatch(of: zipRegex) != nil {
    print("Looks valid")
}
```

Output (tested with Swift 6.4):

```text
Looks valid
```

This practice reduces accidental false positives from partial matches.

### Practice 3: Prefer RegexBuilder for long or business-critical patterns

If a regex is likely to be edited later, builder syntax can make maintenance safer and easier.

```swift
import RegexBuilder

let idRegex = Regex {
    "ID-"
    Repeat(count: 6) {
        CharacterClass.digit
    }
}
```

This version is easier to inspect than a compact literal when requirements change later.

## 8. Limitations and Edge Cases

- Regex can describe text patterns, but it does not automatically guarantee semantic validity. For example, a pattern can match `2025-99-99` even though that is not a real date.
- Some patterns become hard to read quickly. If teammates cannot understand the regex, maintenance becomes harder.
- `firstMatch(of:)` may succeed even when the full input is not valid. This is a common source of bugs in validation code.
- Captured values are based on the groups you define. If you change the pattern structure, capture positions such as `match.1` and `match.2` may change too.
- If you need older platform compatibility or existing Objective-C-based code, you may still encounter `NSRegularExpression`. Swift regex is often nicer to use, but older codebases may not be fully migrated.
- Very large or very complex patterns can be slower or more difficult to debug than a simpler parsing approach.

## 9. Swift Regex Literals vs RegexBuilder

These two approaches solve the same broad problem, but they are best in slightly different situations.

| Approach | Best for | Strength | Tradeoff |
| --- | --- | --- | --- |
| Regex literal | Short, familiar patterns | Compact and quick to write | Can become hard to read |
| RegexBuilder | Longer or structured patterns | Readable, composable, type-safe style | More verbose |

Use a regex literal when the pattern is short and the meaning is obvious, such as finding digits or checking a small code format. Use `RegexBuilder` when the pattern contains several parts, captures, or repeated rules that would be difficult to review in slash syntax.

Compared with `NSRegularExpression`, Swift’s modern regex support usually feels more natural in Swift code. The newer API integrates better with matches and captures, while older Foundation-based regex often requires more manual range handling.

## 10. Practical Mini Project

Let’s build a small validator and parser for order references. The required format is two uppercase letters, a dash, and four digits, such as `AB-4829`.

```swift
import RegexBuilder

func parseOrderCode(_ text: String) {
    let orderRegex = Regex {
        Capture {
            Repeat(count: 2) {
                CharacterClass("A"..."Z")
            }
        }
        "-"
        Capture {
            Repeat(count: 4) {
                CharacterClass.digit
            }
        }
    }

    if let match = text.wholeMatch(of: orderRegex) {
        let prefix = match.1
        let number = match.2

        print("Valid order code")
        print("Prefix:", prefix)
        print("Number:", number)
    } else {
        print("Invalid order code:", text)
    }
}

parseOrderCode("AB-4829")
parseOrderCode("A1-4829")
parseOrderCode("AB-48")
```

Output (tested with Swift 6.4):

```text
Valid order code
Prefix: AB
Number: 4829
Invalid order code: A1-4829
Invalid order code: AB-48
```

This mini project validates the full input and extracts the two meaningful parts. It shows a realistic pattern: check a format, then use captured values in program logic. The regex is built inside the function because `Regex` is not `Sendable`: in the Swift 6 language mode, a nonisolated function cannot read a regex stored in a top-level constant.

## 11. Key Points

- Swift regular expressions are used to match, validate, and extract text patterns.
- Regex literals are concise and useful for short, familiar patterns.
- `RegexBuilder` is often better for long, structured, or frequently edited patterns.
- Use `firstMatch(of:)` to find a matching part and `wholeMatch(of:)` to validate the entire string.
- Captured groups let you extract specific pieces of a match, such as year, month, and day.
- Regex is powerful, but simple string methods are often clearer for simple tasks.

## 12. Practice Exercise

Build a program that checks whether a username follows this rule:

- It must start with a lowercase letter.
- It may contain lowercase letters and digits after that.
- Its total length must be between 4 and 12 characters.

**Expected output:** For a valid username, print `Valid username`. Otherwise, print `Invalid username`.

**Hint:** Use `wholeMatch(of:)` so the entire input must follow the rule.

```swift
let username = "alex42"
let usernameRegex = /[a-z][a-z0-9]{3,11}/

if username.wholeMatch(of: usernameRegex) != nil {
    print("Valid username")
} else {
    print("Invalid username")
}
```

Output (tested with Swift 6.4):

```text
Valid username
```

## 13. Final Summary

Swift regular expressions give you a modern way to work with text patterns directly in Swift. You can use regex literals for concise matching and `RegexBuilder` when you want a more readable and maintainable structure. Together, these tools make it easier to validate input, search strings, and extract useful data from text.

The most important habit is choosing the right level of complexity. Use regular expressions when the problem truly involves a pattern, but prefer simple string APIs when they are clearer. As a next step, practice writing small validation rules and capture-based parsers so you become comfortable deciding between regex literals, `RegexBuilder`, and ordinary string methods.

## Where AI Coding Assistants Get This Wrong

- Often suggested: Writes `Repeat(4) { CharacterClass.digit }` in RegexBuilder.
  Instead: Use `Repeat(count: 4) { .digit }` for an exact count, or `Repeat(2...4) { .digit }` for a range. (The unlabeled initializer takes a `RangeExpression`, so an `Int` literal fails to compile with "requires that 'Int' conform to 'RangeExpression'".)
- Often suggested: Caches compiled patterns in a `static let` or a global `let` 'for performance'.
  Instead: Use a computed property (`static var orderCode: Regex<(Substring, Substring)> { /([A-Z]{2})-\d{4}/ }`), build the regex where it's used, or isolate the cache to an actor with `@MainActor static let`. (`Regex` doesn't conform to `Sendable`, so Swift 6 rejects the global or static `let` as not concurrency-safe.)
- Often suggested: Validates ASCII-only input such as PINs, IDs, or usernames with `\d`, `\w`, or `CharacterClass.digit`.
  Instead: Use explicit ranges like `[0-9]` and `[A-Za-z0-9_]`, or apply `.asciiOnlyDigits()` and `.asciiOnlyWordCharacters()` to the regex. (Swift Regex is Unicode-aware: `"٣٤٥".wholeMatch(of: /\d+/)` succeeds, yet `Int("٣٤٥")` returns `nil`, so validated input can still fail downstream.)
