Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Veloce

Veloce — A high-performance regex library for Swift, powered by Rust's regex crate via swift-bridge.

Veloce (Italian: "fast") - Because your regex should never be the bottleneck.

Swift Platforms License

Experimental: This project is a playground for exploring Swift-Rust interoperability via FFI. While functional and well-tested, it's primarily a learning exercise in bridging these two languages. Use in production at your own discretion.

Why Veloce?

Feature Veloce NSRegularExpression Swift Regex (iOS 16+)
Performance Fastest Slowest Fast
ReDoS Protection Linear time guarantee Vulnerable Some protection
iOS Version 13+ All 16+
SIMD Optimized Yes No Partial
Unicode Full Full Full

Performance Highlights

  • Up to 15x faster than NSRegularExpression for complex patterns
  • 1000x+ faster on ReDoS-vulnerable patterns (linear time guarantee)
  • SIMD-accelerated literal matching via Rust's regex crate
  • Optimized FFI through swift-bridge with minimal overhead

Installation

Swift Package Manager

Add Veloce to your Package.swift:

dependencies: [
    .package(url: "https://github.com/batuhansk/veloce.git", from: "0.1.0")
]

Then add "Veloce" to your target's dependencies:

.target(
    name: "YourApp",
    dependencies: ["Veloce"]
)

Or in Xcode: File → Add Packages → Enter the repository URL.

Note: The package downloads a pre-built XCFramework (~2MB) from GitHub Releases automatically.

Quick Start

import Veloce

// Create a regex
let regex = try Regex(#"\b\w+@\w+\.\w+\b"#)

// Check for matches
if regex.isMatch("Contact: user@example.com") {
    print("Found an email!")
}

// Find matches
if let match = regex.firstMatch(in: "Email: test@example.com") {
    print("Found: \(match.value)")  // "test@example.com"
}

// Find all matches
let emails = regex.allMatches(in: "a@b.c and x@y.z")
print(emails)  // ["a@b.c", "x@y.z"]

// Replace
let censored = regex.replacingAll(
    "Contact user@example.com or admin@site.org",
    with: "[REDACTED]"
)
// "Contact [REDACTED] or [REDACTED]"

// Split
let csv = try Regex(#",\s*"#)
let values = csv.split("a, b,  c,d")  // ["a", "b", "c", "d"]

// Capture groups
let parser = try Regex(#"(\w+)=(\w+)"#)
let captures = parser.captures(in: "name=John")
if !captures.isEmpty {
    print(captures[0])  // "name=John" (full match)
    print(captures[1])  // "name"
    print(captures[2])  // "John"
}

// Count matches efficiently
let count = regex.matchCount(in: "a@b.c and x@y.z")  // 2

// Multi-pattern matching with RegexSet
let keywords = try RegexSet(["error", "warning", "critical"])
if keywords.isMatch("[ERROR] Something went wrong") {
    let matched = keywords.matchingPatterns("[ERROR] Something went wrong")
    print(matched)  // [0] - "error" pattern matched
}

API Reference

Regex

public struct Regex: Sendable {
    /// Creates a compiled regex from a pattern string
    public init(_ pattern: String) throws

    /// Returns true if the pattern matches anywhere in the string
    public func isMatch(_ string: String) -> Bool

    /// Finds the first match
    public func firstMatch(in string: String) -> Match?

    /// Finds all non-overlapping matches (returns matched strings)
    public func allMatches(in string: String) -> [String]

    /// Counts matches without extracting them (faster than allMatches().count)
    public func matchCount(in string: String) -> Int

    /// Replaces the first occurrence
    public func replacing(_ string: String, with replacement: String) -> String

    /// Replaces all occurrences
    public func replacingAll(_ string: String, with replacement: String) -> String

    /// Splits the string by the pattern
    public func split(_ string: String) -> [String]

    /// Extracts all capture groups from the first match
    public func captures(in string: String) -> [String]

    /// Gets a specific capture group (0 = full match, 1 = first group, etc.)
    public func captureGroup(in string: String, group: Int) -> String?

    /// Number of capture groups (including full match)
    public var captureCount: Int
}

RegexSet

public struct RegexSet: Sendable {
    /// Creates a compiled regex set from multiple patterns
    public init(_ patterns: [String]) throws

    /// Returns true if any pattern matches the string
    public func isMatch(_ string: String) -> Bool

    /// Returns indices of all patterns that matched
    public func matchingPatterns(_ string: String) -> [Int]

    /// Number of patterns in the set
    public var patternCount: Int
}

Match

public struct Match: Sendable, Equatable {
    /// The matched text
    public let value: String

    /// Byte range in the original string
    public let byteRange: Range<Int>

    /// Get the range as String.Index values
    public func range(in string: String) -> Range<String.Index>?
}

RegexError

public enum RegexError: Error {
    case invalidPattern(String)
}

Pattern Syntax

Veloce uses Rust's regex syntax, which is similar to Perl/PCRE but with some differences:

// Basics
try Regex(#"hello"#)           // Literal match
try Regex(#"hel+o"#)           // One or more 'l'
try Regex(#"hel*o"#)           // Zero or more 'l'
try Regex(#"hel?o"#)           // Optional 'l'
try Regex(#"[a-z]+"#)          // Character class
try Regex(#"\d+"#)             // Digit shorthand
try Regex(#"\w+"#)             // Word character
try Regex(#"\s+"#)             // Whitespace

// Anchors
try Regex(#"^start"#)          // Start of string
try Regex(#"end$"#)            // End of string
try Regex(#"\bword\b"#)        // Word boundary

// Groups
try Regex(#"(a|b)"#)           // Alternation
try Regex(#"(\w+)"#)           // Capture group
try Regex(#"(?:\w+)"#)         // Non-capturing group
try Regex(#"(?<name>\w+)"#)    // Named capture group

// Flags (inline)
try Regex(#"(?i)hello"#)       // Case insensitive
try Regex(#"(?m)^line"#)       // Multiline mode
try Regex(#"(?s)a.b"#)         // Dot matches newline

Note: Lookahead/lookbehind assertions ((?=...), (?!...), (?<=...), (?<!...)) are not supported by the Rust regex crate for performance reasons (they would break the linear time guarantee).

For full syntax documentation, see the Rust regex crate docs.

ReDoS Protection

Unlike most regex engines, Veloce is immune to ReDoS attacks thanks to Rust's regex crate which guarantees linear time matching:

// This pattern causes exponential backtracking in many engines
let evil = try Regex(#"(a+)+b"#)

// With 30 'a's, this would hang NSRegularExpression for hours
// Veloce completes in microseconds
let input = String(repeating: "a", count: 30)
_ = evil.isMatch(input)  // Fast!

This makes Veloce safe to use with untrusted patterns or inputs.

Building from Source

Prerequisites

  • Xcode 15+ with Swift 5.9+
  • Rust toolchain (install from rustup.rs)
  • iOS/macOS SDK

For Contributors

# Clone the repository
git clone https://github.com/batuhansk/veloce.git
cd veloce

# Build the XCFramework (includes generating Swift bindings)
./scripts/build-xcframework.sh

# For local development, update Package.swift to use local binary:
# Comment out the URL-based binaryTarget and uncomment the path-based one

# Run tests
swift test

# Run benchmarks (use release mode for accurate results)
swift test -c release --filter Benchmark

How the Build Works

  1. Rust compilation: cargo build compiles the Rust library for each Apple platform
  2. swift-bridge generation: During cargo build, the build.rs script invokes swift-bridge to:
    • Parse #[swift_bridge::bridge] macros in rust/src/lib.rs
    • Generate Swift bindings in rust/generated/
    • Generate C headers for the FFI layer
  3. Copy bindings: The build script copies generated .swift files to Sources/Veloce/Generated/
  4. XCFramework creation: xcodebuild -create-xcframework packages the libraries

Regenerating Bindings Only

If you only need to regenerate the Swift bindings without a full rebuild:

./scripts/generate-bindings.sh

Creating a Release

For maintainers preparing a new release:

# 1. Prepare the release (builds XCFramework, creates zip, computes checksum)
./scripts/prepare-release.sh 0.1.0

# 2. Update Package.swift with the checksum from the script output

# 3. Commit, tag, and push
git add -A
git commit -m "Release 0.1.0"
git tag 0.1.0
git push origin main --tags

# 4. Create GitHub Release and upload VeloceCore.xcframework.zip

Benchmarks

Test Environment

  • Device: MacBook Pro
  • Chip: Apple M2 Max (12 cores: 8 performance + 4 efficiency)
  • Memory: 32 GB
  • OS: macOS Tahoe 26.0

Results

Benchmark Veloce NSRegularExpression Speedup
Simple literal (1K matches) 0.49ms 0.69ms 1.4x
Email validation (10K emails) 1.4ms 7.0ms 5.0x
Find all in 60KB text 2.7ms 27.5ms 10.2x
Log parsing with captures 0.31ms 4.6ms 14.8x
Replace all digits 1.8ms 1.8ms ~1.0x
Split CSV (1K values) 5.6ms 21.2ms 3.8x
ReDoS pattern (30 'a's) 0.17ms 191ms* 1124x
Batch vs Individual captures 0.68ms 3.6ms 5.3x

*NSRegularExpression tested with only 15 'a's (would hang with 30)

Key Findings

  • Log parsing shows the biggest improvement (14.8x) due to Rust's optimized capture group handling
  • Find all is 10x faster thanks to byte-range FFI optimization (avoids string allocations)
  • ReDoS resistance is the standout feature (1124x faster) - Veloce handles pathological patterns in microseconds while NSRegularExpression exhibits exponential backtracking
  • Split is 3.8x faster thanks to optimized byte-range FFI (was 0.8x slower before optimization)
  • Batch captures are 5.3x faster than extracting groups individually

Run Benchmarks Yourself

# Run in release mode for accurate results
swift test -c release --filter Benchmark

Note: Results vary by hardware. Run on your own device for accurate comparisons.

Migration from NSRegularExpression

// Before (NSRegularExpression)
let pattern = #"\d+"#
let regex = try NSRegularExpression(pattern: pattern)
let range = NSRange(text.startIndex..., in: text)
if regex.firstMatch(in: text, range: range) != nil {
    print("Found!")
}

// After (Veloce)
let regex = try Regex(#"\d+"#)
if regex.isMatch(text) {
    print("Found!")
}

// Bonus: Multi-pattern matching (no NSRegularExpression equivalent)
let keywords = try RegexSet(["error", "warning", "critical"])
if keywords.isMatch(logLine) {
    // Check which patterns matched
    let indices = keywords.matchingPatterns(logLine)
}

Thread Safety

Regex and RegexSet are both Sendable and can be safely shared across threads. Pattern compilation is expensive, so compile once and reuse:

// Good: Compile once
let emailRegex = try Regex(#"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"#)

await withTaskGroup(of: Bool.self) { group in
    for email in emails {
        group.addTask {
            emailRegex.isMatch(email)  // Safe concurrent access
        }
    }
}

License

MIT License - see LICENSE for details.

Acknowledgments

About

Exploring Swift-Rust FFI with a high-performance regex library

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages