Regexp #
Regular expressions are a very powerful tool for pattern matching, validation, and text extraction — but also one of the easiest to misuse. The regexp package in Go implements the RE2 syntax, which differs from PCRE (Perl-Compatible Regular Expressions) commonly used in other languages: RE2 guarantees linear execution time O(n) against the input length, meaning there’s no such thing as “catastrophic backtracking” that can hang a program when receiving malicious input. This makes Go regexes safe to use in production applications that accept user input. This article covers how to use regexp effectively — from compilation and basic matching, to capture groups, dynamic replacement, and when not to use regex at all.
An Overview of the regexp Package #
flowchart TD
R["package regexp"] --> Compile["Regex Compilation"]
R --> Match["Matching"]
R --> Find["Search & Extraction"]
R --> Replace["Replacement"]
R --> Split["Splitting"]
Compile --> C1["regexp.Compile(pattern)\nreturns *Regexp, error"]
Compile --> C2["regexp.MustCompile(pattern)\npanics on error\nfor constant patterns"]
Compile --> C3["regexp.CompilePOSIX\nPOSIX semantics"]
Match --> M1["re.MatchString(s)\nbool — matches or not"]
Match --> M2["re.Match([]byte)\nbool — for []byte"]
Find --> F1["re.FindString\nthe first match"]
Find --> F2["re.FindAllString\nall matches"]
Find --> F3["re.FindStringSubmatch\nmatch + capture groups"]
Find --> F4["re.FindAllStringSubmatch\nall + capture groups"]
Find --> F5["re.FindStringIndex\nthe first match position"]
Replace --> RP1["re.ReplaceAllString\nreplace all matches"]
Replace --> RP2["re.ReplaceAllLiteralString\nwithout $ expansion"]
Replace --> RP3["re.ReplaceAllStringFunc\nreplace with a function"]
Split --> SP1["re.Split\nsplit by a pattern"]
style R fill:#4f86c6,color:#fff
style Compile fill:#e8f5e9
style Match fill:#e3f2fd
style Find fill:#fff3e0
style Replace fill:#f3e5f5
style Split fill:#fce4ecCompiling Regexes #
Before it can be used, a regex pattern must be compiled into a *regexp.Regexp object. Compilation is expensive — always do it once, usually as a package variable:
package main
import (
"fmt"
"regexp"
)
// CORRECT: compile once as package variables (global level)
var (
reEmail = regexp.MustCompile(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`)
rePhone = regexp.MustCompile(`^(\+62|62|0)8[1-9][0-9]{6,9}$`)
reZipCode = regexp.MustCompile(`^\d{5}$`)
reURLPath = regexp.MustCompile(`^/[a-zA-Z0-9/\-._~:@!$&'()*+,;=%?#]*$`)
)
// ANTI-PATTERN: compile inside a frequently called function
func validateEmailBad(email string) bool {
re := regexp.MustCompile(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`)
// re is compiled every time the function is called — very slow!
return re.MatchString(email)
}
// CORRECT: use the already-compiled variable
func validateEmail(email string) bool {
return reEmail.MatchString(email)
}
func main() {
// Compile — returns an error if the pattern is invalid
re, err := regexp.Compile(`(\w+)`)
if err != nil {
fmt.Println("invalid regex pattern:", err)
return
}
fmt.Println(re.FindString("Hello World")) // Hello
// MustCompile — panics if the pattern is invalid
// Use ONLY for patterns you wrote yourself (not from user input!)
re2 := regexp.MustCompile(`\d+`)
fmt.Println(re2.FindString("abc 123 def")) // 123
// DON'T: MustCompile with user input
// userPattern := os.Stdin.ReadString('\n')
// re3 := regexp.MustCompile(userPattern) // panics if the input is invalid!
// CORRECT for user input: Compile and handle the error
// re3, err := regexp.Compile(userPattern)
}
Go Regex Syntax (RE2) #
Go uses the RE2 syntax, not PCRE. The most important differences: no lookahead/lookbehind and no backreferences:
flowchart LR
subgraph Basic["Basic Characters"]
D1[". — any character except newline"]
D2["\d — a digit [0-9]"]
D3["\w — a word char [a-zA-Z0-9_]"]
D4["\s — whitespace"]
D5["\D \W \S — negations of the above"]
end
subgraph Quantifiers["Quantifiers"]
K1["* — 0 or more"]
K2["+ — 1 or more"]
K3["? — 0 or 1"]
K4["{n} — exactly n"]
K5["{n,m} — n to m"]
K6["{n,} — at least n"]
K7["*? +? ?? — non-greedy"]
end
subgraph Anchors["Anchors"]
A1["^ — start of the string (or line with (?m))"]
A2["$ — end of the string (or line with (?m))"]
A3["\b — word boundary"]
A4["\A — start of the string (always)"]
A5["\z — end of the string (always)"]
end
subgraph Groups["Groups & Alternation"]
G1["(abc) — a capturing group"]
G2["(?:abc) — a non-capturing group"]
G3["(?P<name>abc) — a named capturing group"]
G4["a|b — alternation: a or b"]
G5["[abc] — a character set"]
G6["[^abc] — a negated character set"]
end
subgraph Flags["Flags"]
F1["(?i) — case-insensitive"]
F2["(?m) — multiline (^ and $ match per line)"]
F3["(?s) — . matches newlines too"]
F4["(?U) — non-greedy by default"]
end// Regex syntax examples
re := regexp.MustCompile(`\d+`) // one or more digits
re2 := regexp.MustCompile(`[a-z]+`) // one or more lowercase letters
re3 := regexp.MustCompile(`(foo|bar)`) // "foo" or "bar"
re4 := regexp.MustCompile(`\b\w+\b`) // a whole word
re5 := regexp.MustCompile(`(?i)hello`) // "hello" case-insensitive
re6 := regexp.MustCompile(`(?m)^\d+`) // digits at the start of every line
re7 := regexp.MustCompile(`(?s).+`) // all characters including newlines
re8 := regexp.MustCompile(`(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})`)
// Characters that need escaping in regex:
// . * + ? ^ $ {} [] | () \
// Use regexp.QuoteMeta for automatic escaping
userInput := "price $10.99 (discount)"
pattern := regexp.QuoteMeta(userInput) // "price \$10\.99 \(discount\)"
re9 := regexp.MustCompile(pattern)
fmt.Println(re9.MatchString(userInput)) // true
MatchString — Basic Matching #
var reEmail = regexp.MustCompile(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`)
// MatchString — does the string match the pattern?
fmt.Println(reEmail.MatchString("[email protected]")) // true
fmt.Println(reEmail.MatchString("not-an-email")) // false
fmt.Println(reEmail.MatchString("@nodomain.com")) // false
// The top-level regexp.MatchString function — compiles on every call, avoid in production
matched, err := regexp.MatchString(`^\d+$`, "12345")
fmt.Println(matched, err) // true <nil>
// Match for []byte
data := []byte("Hello 123")
re := regexp.MustCompile(`\d+`)
fmt.Println(re.Match(data)) // true
FindString — Finding Matching Text #
re := regexp.MustCompile(`\d+`)
text := "I have 3 cats and 12 dogs"
// FindString — return the first match (an empty string if none)
fmt.Println(re.FindString(text)) // "3"
// FindAllString — return all matches
all := re.FindAllString(text, -1) // -1 = no limit
fmt.Println(all) // [3 12]
// Limit the number of matches
two := re.FindAllString(text, 2)
fmt.Println(two) // [3 12]
// FindStringIndex — the (start, end) position of the first match
idx := re.FindStringIndex(text)
fmt.Println(idx) // [11 12] — the position of "3"
fmt.Println(text[idx[0]:idx[1]]) // "3"
// FindAllStringIndex — the positions of all matches
all2 := re.FindAllStringIndex(text, -1)
fmt.Println(all2) // [[11 12] [22 24]]
// FindReaderIndex — read from an io.Reader
reader := strings.NewReader(text)
idxR := re.FindReaderIndex(reader)
fmt.Println(idxR) // [11 12]
Capture Groups — Extracting Specific Parts #
Capture groups (...) allow extracting specific parts of matching text:
flowchart LR
Pattern["Pattern:<br/>(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})"] --> Input["Input: '2024-03-15'"]
Input --> SM["FindStringSubmatch"]
SM --> R0["[0] '2024-03-15'<br/>the whole match"]
SM --> R1["[1] '2024'<br/>group 1: year"]
SM --> R2["[2] '03'<br/>group 2: month"]
SM --> R3["[3] '15'<br/>group 3: day"]
SM --> Named["SubexpIndex('year')\n→ the named group index"]
style Pattern fill:#4f86c6,color:#fff
style R0 fill:#e8f5e9
style R1 fill:#e3f2fd
style R2 fill:#fff3e0
style R3 fill:#f3e5f5// A pattern with named capturing groups
reDate := regexp.MustCompile(
`(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})`)
// FindStringSubmatch — returns [whole match, group1, group2, ...]
match := reDate.FindStringSubmatch("Date: 2024-03-15")
if match != nil {
fmt.Println("Whole match:", match[0]) // "2024-03-15"
fmt.Println("Year:", match[1]) // "2024"
fmt.Println("Month:", match[2]) // "03"
fmt.Println("Day:", match[3]) // "15"
// Access named groups with SubexpIndex
yearIdx := reDate.SubexpIndex("year")
monthIdx := reDate.SubexpIndex("month")
dayIdx := reDate.SubexpIndex("day")
fmt.Printf("Year=%s, Month=%s, Day=%s\n",
match[yearIdx], match[monthIdx], match[dayIdx])
}
// More idiomatic: build a map from named groups
func submatchMap(re *regexp.Regexp, s string) map[string]string {
match := re.FindStringSubmatch(s)
if match == nil {
return nil
}
result := make(map[string]string)
for i, name := range re.SubexpNames() {
if i != 0 && name != "" {
result[name] = match[i]
}
}
return result
}
// Usage
fields := submatchMap(reDate, "2024-03-15")
fmt.Println(fields["year"]) // "2024"
fmt.Println(fields["month"]) // "03"
fmt.Println(fields["day"]) // "15"
// FindAllStringSubmatch — all matches with their groups
reNumbers := regexp.MustCompile(`(\d+)\.(\d+)`)
text := "Price: 15.000 and 250.000"
all := reNumbers.FindAllStringSubmatch(text, -1)
for _, m := range all {
fmt.Printf("Match: %s, Parts: %s.%s\n", m[0], m[1], m[2])
}
// Match: 15.000, Parts: 15.000
// Match: 250.000, Parts: 250.000
ReplaceAll — Text Replacement #
re := regexp.MustCompile(`\d+`)
// ReplaceAllString — replace all matches with a fixed string
result := re.ReplaceAllString("abc 123 def 456", "NUM")
fmt.Println(result) // "abc NUM def NUM"
// ReplaceAllString with group references ($1, $2, ...)
reName := regexp.MustCompile(`(\w+)\s+(\w+)`)
result2 := reName.ReplaceAllString("Budi Santoso", "$2, $1")
fmt.Println(result2) // "Santoso, Budi"
// ReplaceAllLiteralString — doesn't process $ as a group reference
result3 := re.ReplaceAllLiteralString("abc 123 def", "$1")
fmt.Println(result3) // "abc $1 def" — $ isn't expanded
// ReplaceAllStringFunc — replace with the result of a function
result4 := re.ReplaceAllStringFunc("abc 123 def 456", func(match string) string {
n, _ := strconv.Atoi(match)
return strconv.Itoa(n * 2) // double every number
})
fmt.Println(result4) // "abc 246 def 912"
// Example: masking credit card numbers
reCreditCard := regexp.MustCompile(`\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b`)
logText := "Payment with card 4111 1111 1111 1111 successful"
safeText := reCreditCard.ReplaceAllStringFunc(logText, func(match string) string {
// Only show the last 4 digits
clean := regexp.MustCompile(`[\s-]`).ReplaceAllString(match, "")
return "**** **** **** " + clean[len(clean)-4:]
})
fmt.Println(safeText) // "Payment with card **** **** **** 1111 successful"
// ReplaceAllFunc for []byte
re2 := regexp.MustCompile(`[aeiou]`)
result5 := re2.ReplaceAllFunc([]byte("hello world"), func(b []byte) []byte {
return bytes.ToUpper(b)
})
fmt.Println(string(result5)) // "hEllO wOrld"
Split — Splitting Text #
// Split by a regex pattern
re := regexp.MustCompile(`[\s,;]+`) // whitespace, comma, or semicolon
text := "apple, mango; orange durian"
fruits := re.Split(text, -1) // -1 = no limit
fmt.Println(fruits) // [apple mango orange durian]
// Limit the number of parts
parts := re.Split("a,b,c,d,e", 3)
fmt.Println(parts) // [a b c,d,e] — at most 3 parts
// Split with capture groups — the separator is included
reSeparator := regexp.MustCompile(`(\d+)`)
result := reSeparator.Split("abc123def456ghi", -1)
fmt.Println(result) // [abc def ghi] — the numbers (separators) aren't included
// To include separators, use FindAllStringIndex manually
Common Validation Patterns #
// A collection of ready-to-use validators
var (
// Email — simple, not the complex RFC 5322
ValidEmail = regexp.MustCompile(
`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`)
// Indonesian phone numbers
ValidPhone = regexp.MustCompile(
`^(\+62|62|0)(811|812|813|821|822|823|851|852|853|` +
`814|815|816|855|856|857|858|` +
`817|818|819|859|877|878|` +
`831|832|833|838|` +
`895|896|897|898|899|` +
`881|882|883|884|885|886|887|888|889)\d{5,8}$`)
// Indonesian National ID (NIK) — 16 digits
ValidNIK = regexp.MustCompile(`^\d{16}$`)
// NPWP — format XX.XXX.XXX.X-XXX.XXX
ValidNPWP = regexp.MustCompile(
`^\d{2}\.\d{3}\.\d{3}\.\d-\d{3}\.\d{3}$`)
// Indonesian postal codes
ValidPostalCode = regexp.MustCompile(`^[1-9]\d{4}$`)
// Vehicle license plates (simple)
ValidPlate = regexp.MustCompile(
`(?i)^[A-Z]{1,2}\s?\d{1,4}\s?[A-Z]{1,3}$`)
// URL slugs
ValidSlug = regexp.MustCompile(`^[a-z0-9]+(?:-[a-z0-9]+)*$`)
// UUID v4
ValidUUID = regexp.MustCompile(
`^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`)
// IPv4
ValidIPv4 = regexp.MustCompile(
`^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$`)
// CSS hex colors (#RGB or #RRGGBB)
ValidHexColor = regexp.MustCompile(`^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$`)
)
func validateInput(email, phone, nik string) []string {
var errors []string
if !ValidEmail.MatchString(email) {
errors = append(errors, "invalid email format")
}
if !ValidPhone.MatchString(phone) {
errors = append(errors, "invalid phone number")
}
if !ValidNIK.MatchString(nik) {
errors = append(errors, "NIK must be 16 digits")
}
return errors
}
Common Parsing Patterns #
// Parse a log format: [2024-03-15 14:30:00] LEVEL message
reLog := regexp.MustCompile(
`\[(?P<time>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\]\s+` +
`(?P<level>\w+)\s+(?P<message>.+)`)
func parseLogLine(line string) (time, level, message string, ok bool) {
m := reLog.FindStringSubmatch(line)
if m == nil {
return "", "", "", false
}
names := reLog.SubexpNames()
for i, name := range names {
switch name {
case "time":
time = m[i]
case "level":
level = m[i]
case "message":
message = m[i]
}
}
return time, level, message, true
}
// Parse a simple URL
reURL := regexp.MustCompile(
`(?P<scheme>https?)://(?P<host>[^/:]+)(?::(?P<port>\d+))?(?P<path>/[^?#]*)?(?:\?(?P<query>[^#]*))?(?:#(?P<fragment>.*))?`)
// Parse a simple CSV (without quoting)
reCsv := regexp.MustCompile(`,\s*`)
func parseCSVLine(line string) []string {
return reCsv.Split(line, -1)
}
// Extract all URLs from HTML
reLink := regexp.MustCompile(`href="([^"]+)"`)
func extractLinks(html string) []string {
matches := reLink.FindAllStringSubmatch(html, -1)
links := make([]string, 0, len(matches))
for _, m := range matches {
links = append(links, m[1]) // m[1] is the first capture group
}
return links
}
Performance: When Not to Use Regex #
flowchart TD
Q{"What needs\nto be done?"} --> Simple["Simple operations:\ncheck prefix/suffix,\nfind a substring,\nsplit with a fixed delimiter"]
Q --> Complex["Complex pattern matching,\nirregular formats,\nmany variations"]
Simple --> NoRegex["Use the strings package\nFASTER and CLEARER"]
Complex --> Regex["Consider Regex\n(but measure first)"]
NoRegex --> NS1["strings.HasPrefix / HasSuffix"]
NoRegex --> NS2["strings.Contains / Index"]
NoRegex --> NS3["strings.Split / Fields"]
NoRegex --> NS4["strings.TrimSpace / Trim"]
Regex --> RA1["Format validation: email, URL, dates"]
Regex --> RA2["Pattern extraction from free text"]
Regex --> RA3["Complex pattern replacement"]
style NoRegex fill:#e8f5e9
style Regex fill:#e3f2fd// Performance comparison for simple operations
// SLOW: regex for simple operations
rePrefix := regexp.MustCompile(`^http://`)
rePrefix.MatchString(url) // compilation + matching engine overhead
// FAST: the strings package for simple operations
strings.HasPrefix(url, "http://") // direct, no overhead
// SLOW: regex for splitting with a fixed delimiter
reComma := regexp.MustCompile(`,`)
reComma.Split(csv, -1)
// FAST: strings.Split
strings.Split(csv, ",")
// Typical benchmarks:
// strings.HasPrefix: ~5 ns/op
// regexp.MatchString: ~150 ns/op (30x slower for simple cases)
// For complex patterns, the difference isn't significant because regex is genuinely needed
// When regex IS genuinely needed:
// - Highly variable formats (dates in various formats)
// - Complex validation (email, URLs with many edge cases)
// - Extraction from unstructured text
// - Replacement with complex conditions
Security: Regex from User Input #
// DANGEROUS: MustCompile with user input
func searchWithPattern(text, userPattern string) []string {
re := regexp.MustCompile(userPattern) // PANICS if the pattern is invalid!
return re.FindAllString(text, -1)
}
// SAFE: Compile with error handling
func searchWithPatternSafe(text, userPattern string) ([]string, error) {
// Limit the pattern length to prevent ReDoS (doesn't apply to RE2,
// but limiting complexity is still good practice)
if len(userPattern) > 1000 {
return nil, fmt.Errorf("regex pattern too long")
}
re, err := regexp.Compile(userPattern)
if err != nil {
return nil, fmt.Errorf("invalid regex pattern: %w", err)
}
return re.FindAllString(text, 100), nil // limit the result count too
}
// NOTE: Go uses RE2, which is NOT vulnerable to ReDoS
// (Catastrophic Backtracking) because it's guaranteed O(n).
// Unlike PCRE in PHP, Python, and Java, which can hang with certain patterns.
// Still, limit user-supplied patterns as good security practice.
Production Usage Patterns #
Input Sanitization Middleware #
var (
reDangerousChars = regexp.MustCompile(`[<>'"&;]`)
reMultipleSpaces = regexp.MustCompile(`\s{2,}`)
reNonAlphanumeric = regexp.MustCompile(`[^a-zA-Z0-9\s]`)
)
func sanitizeInput(input string) string {
// Remove dangerous characters for HTML
clean := reDangerousChars.ReplaceAllString(input, "")
// Normalize excessive spaces
clean = reMultipleSpaces.ReplaceAllString(clean, " ")
return strings.TrimSpace(clean)
}
func makeSlug(title string) string {
// Convert to lowercase
slug := strings.ToLower(title)
// Replace non-alphanumeric characters with -
slug = reNonAlphanumeric.ReplaceAllString(slug, "-")
// Normalize multiple -
reDash := regexp.MustCompile(`-+`)
slug = reDash.ReplaceAllString(slug, "-")
// Remove - at the start and end
return strings.Trim(slug, "-")
}
// Usage
fmt.Println(makeSlug("Go Guide: Standard Library Package!"))
// go-guide-standard-library-package
A Log Parser with Named Groups #
// Nginx log format: 192.168.1.1 - - [15/Mar/2024:14:30:00 +0700] "GET /api/v1 HTTP/1.1" 200 1234
var reNginxLog = regexp.MustCompile(
`(?P<ip>\d+\.\d+\.\d+\.\d+) - - ` +
`\[(?P<time>[^\]]+)\] ` +
`"(?P<method>\w+) (?P<path>[^ ]+) HTTP/[\d.]+" ` +
`(?P<status>\d+) (?P<bytes>\d+)`)
type NginxLogEntry struct {
IP string
Time string
Method string
Path string
Status int
Bytes int
}
func parseNginxLog(line string) (*NginxLogEntry, error) {
match := reNginxLog.FindStringSubmatch(line)
if match == nil {
return nil, fmt.Errorf("unrecognized log format: %q", line)
}
names := reNginxLog.SubexpNames()
fields := make(map[string]string)
for i, name := range names {
if name != "" {
fields[name] = match[i]
}
}
status, _ := strconv.Atoi(fields["status"])
bytes, _ := strconv.Atoi(fields["bytes"])
return &NginxLogEntry{
IP: fields["ip"],
Time: fields["time"],
Method: fields["method"],
Path: fields["path"],
Status: status,
Bytes: bytes,
}, nil
}
Redacting Sensitive Data #
var (
reCreditCard2 = regexp.MustCompile(`\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b`)
reNIK2 = regexp.MustCompile(`\b\d{16}\b`)
reEmail3 = regexp.MustCompile(`[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}`)
rePhone2 = regexp.MustCompile(`\b0\d{9,11}\b`)
)
func redactSensitiveData(text string) string {
// Mask credit cards
text = reCreditCard2.ReplaceAllStringFunc(text, func(m string) string {
clean := regexp.MustCompile(`[\s-]`).ReplaceAllString(m, "")
return "**** **** **** " + clean[len(clean)-4:]
})
// Mask NIKs
text = reNIK2.ReplaceAllStringFunc(text, func(m string) string {
return m[:6] + "**********"
})
// Mask emails
text = reEmail3.ReplaceAllStringFunc(text, func(m string) string {
parts := strings.Split(m, "@")
if len(parts) != 2 {
return m
}
name := parts[0]
if len(name) > 2 {
name = name[:2] + strings.Repeat("*", len(name)-2)
}
return name + "@" + parts[1]
})
return text
}
// Usage
log := "NIK: 3273011234567890, Email: [email protected], Card: 4111 1111 1111 1111"
fmt.Println(redactSensitiveData(log))
// NIK: 327301**********, Email: bu*************@gmail.com, Card: **** **** **** 1111
When to Switch to Alternatives #
Keep using regexp if:
✓ Validating complex formats: email, URLs, dates in various formats
✓ Extracting patterns from unstructured text: logs, HTML, documents
✓ Complex pattern replacement with dynamic conditions
✓ Parsing formats with many variations
Use the strings package if:
✗ Finding a fixed substring → strings.Contains, strings.Index
✗ Splitting with a fixed delimiter → strings.Split
✗ Checking prefixes/suffixes → strings.HasPrefix, strings.HasSuffix
✗ Removing whitespace → strings.TrimSpace
✗ Replacing a fixed string → strings.ReplaceAll
(all of these are 10-100x faster than the regex equivalents)
Use strconv if:
✗ Validating numbers → strconv.Atoi, strconv.ParseFloat
✗ Parsing numbers from strings → more precise and clear
Consider dedicated parsers if:
✗ Parsing HTML/XML → golang.org/x/net/html or encoding/xml
✗ Parsing JSON → encoding/json
✗ Parsing YAML → gopkg.in/yaml.v3
✗ Parsing very complex formats → a structured parser is more maintainable
Summary #
- Compile regexes once as package variables with
MustCompile— don’t compile inside frequently called functions; the overhead is significant.MustCompileonly for constant patterns you wrote yourself — for patterns from user input, always useCompileand handle the error.- Go uses RE2, not PCRE — no lookahead/lookbehind, no backreferences, but guaranteed safe from ReDoS because of its
O(n)complexity.- Named capturing groups with
(?P<name>...)andSubexpIndex("name")make code much more readable than accessingmatch[1],match[2]numerically.ReplaceAllStringFuncfor dynamic replacement — use it when the replacement value depends on the match content, like number transformations or data masking.regexp.QuoteMeta(s)to use a literal string as a pattern — important when the pattern contains special regex characters from user input.- Measure before using regex — for simple operations like prefix checks or fixed-delimiter splits, the
stringspackage is 10-100x faster.FindAllStringSubmatchreturns a slice of slices —result[i][0]is the i-th match,result[i][1]is the first group of the i-th match.- Regex compilation isn’t safe for concurrent use — but executing an already-compiled
*regexp.Regexpis safe from many goroutines at once.