Skip to main content

strings · strconv · unicode · regexp Packages — Complete String Processing

Go's standard library provides everything you need for string processing. Use strings for basic manipulation, strconv for type conversion, unicode for character classification, and regexp for regular expressions.

strings Package — String Manipulation

Searching and Checking

package main

import (
"fmt"
"strings"
)

func main() {
s := "Hello, Go World!"

// Containment checks
fmt.Println(strings.Contains(s, "Go")) // true
fmt.Println(strings.ContainsAny(s, "aeiou")) // true — any character matches
fmt.Println(strings.ContainsRune(s, 'W')) // true — specific rune

// Prefix/suffix
fmt.Println(strings.HasPrefix(s, "Hello")) // true
fmt.Println(strings.HasSuffix(s, "World!")) // true

// Counting
fmt.Println(strings.Count(s, "l")) // 3 — count of 'l'
fmt.Println(strings.Count(s, "o")) // 2 — count of 'o'

// Finding index
fmt.Println(strings.Index(s, "Go")) // 7 — first occurrence
fmt.Println(strings.LastIndex(s, "o")) // 15 — last occurrence
fmt.Println(strings.IndexAny(s, "aeiou")) // 1 — first vowel position
fmt.Println(strings.IndexRune(s, 'W')) // 11 — rune position
}

Transformation and Modification

package main

import (
"fmt"
"strings"
)

func main() {
// Case conversion
fmt.Println(strings.ToUpper("hello")) // HELLO
fmt.Println(strings.ToLower("WORLD")) // world

// Trimming whitespace
s := " Hello, Go! "
fmt.Println(strings.TrimSpace(s)) // "Hello, Go!"
fmt.Println(strings.Trim(s, " ")) // "Hello, Go!"
fmt.Println(strings.TrimLeft(s, " ")) // "Hello, Go! "
fmt.Println(strings.TrimRight(s, " ")) // " Hello, Go!"
fmt.Println(strings.TrimPrefix("Hello, Go!", "Hello, ")) // "Go!"
fmt.Println(strings.TrimSuffix("Hello, Go!", ", Go!")) // "Hello"

// Replacement
text := "foo bar foo baz foo"
fmt.Println(strings.Replace(text, "foo", "qux", 2)) // "qux bar qux baz foo" — first 2
fmt.Println(strings.ReplaceAll(text, "foo", "qux")) // "qux bar qux baz qux" — all

// Repetition
fmt.Println(strings.Repeat("Go! ", 3)) // "Go! Go! Go! "

// Splitting
csv := "apple,banana,cherry"
parts := strings.Split(csv, ",")
fmt.Println(parts) // [apple banana cherry]
fmt.Println(len(parts)) // 3

// Split into at most n parts
fmt.Println(strings.SplitN(csv, ",", 2)) // [apple banana,cherry]

// Split on whitespace (handles multiple spaces)
words := strings.Fields(" foo bar baz ")
fmt.Println(words) // [foo bar baz]

// Joining
fmt.Println(strings.Join(parts, " | ")) // "apple | banana | cherry"

// Cut — Go 1.18+
before, after, found := strings.Cut("user=Alice", "=")
fmt.Println(before, after, found) // user Alice true
}

strings.Builder — Efficient String Construction

package main

import (
"fmt"
"strings"
)

func buildSQL(table string, conditions []string) string {
var b strings.Builder

b.WriteString("SELECT * FROM ")
b.WriteString(table)

if len(conditions) > 0 {
b.WriteString(" WHERE ")
for i, cond := range conditions {
if i > 0 {
b.WriteString(" AND ")
}
b.WriteString(cond)
}
}
b.WriteByte(';')

return b.String()
}

func main() {
sql := buildSQL("users", []string{"age > 18", "active = true", "country = 'KR'"})
fmt.Println(sql)
// SELECT * FROM users WHERE age > 18 AND active = true AND country = 'KR';

// strings.Builder — much faster than + concatenation (minimizes memory allocation)
var sb strings.Builder
for i := 0; i < 5; i++ {
fmt.Fprintf(&sb, "item %d\n", i+1) // fmt.Fprintf works too
}
fmt.Print(sb.String())
}

strconv Package — Type Conversion

package main

import (
"fmt"
"strconv"
)

func main() {
// Integer ↔ string
s := strconv.Itoa(42) // int → string: "42"
n, err := strconv.Atoi("123") // string → int
if err != nil {
fmt.Println("conversion failed:", err)
}
fmt.Println(s, n) // "42" 123

// Format integer in various bases
fmt.Println(strconv.FormatInt(255, 2)) // "11111111" — binary
fmt.Println(strconv.FormatInt(255, 8)) // "377" — octal
fmt.Println(strconv.FormatInt(255, 16)) // "ff" — hexadecimal

// String → integer (specify base)
v, _ := strconv.ParseInt("ff", 16, 64) // parse hex
fmt.Println(v) // 255
v2, _ := strconv.ParseInt("11111111", 2, 64) // parse binary
fmt.Println(v2) // 255

// Float conversion
fs := strconv.FormatFloat(3.14159, 'f', 2, 64) // "3.14" — 2 decimal places
fmt.Println(fs)
fv, _ := strconv.ParseFloat("3.14", 64)
fmt.Println(fv) // 3.14

// Boolean conversion
bs := strconv.FormatBool(true) // "true"
bv, _ := strconv.ParseBool("true") // true
fmt.Println(bs, bv)

// String escaping
quoted := strconv.Quote(`He said "Hello"`)
fmt.Println(quoted) // "He said \"Hello\""
unquoted, _ := strconv.Unquote(`"He said \"Hello\""`)
fmt.Println(unquoted) // He said "Hello"

// Check error type
_, err = strconv.Atoi("abc")
if numErr, ok := err.(*strconv.NumError); ok {
fmt.Println("func:", numErr.Func) // Atoi
fmt.Println("input:", numErr.Num) // abc
fmt.Println("error:", numErr.Err) // invalid syntax
}
}

unicode Package — Character Classification and Conversion

package main

import (
"fmt"
"unicode"
"unicode/utf8"
)

func main() {
chars := []rune{'A', 'z', '5', ' ', '\t', '한', '!'}

for _, r := range chars {
fmt.Printf("'%c' — letter:%v digit:%v space:%v upper:%v lower:%v\n",
r,
unicode.IsLetter(r),
unicode.IsDigit(r),
unicode.IsSpace(r),
unicode.IsUpper(r),
unicode.IsLower(r),
)
}

// Case conversion
fmt.Println(string(unicode.ToUpper('a'))) // A
fmt.Println(string(unicode.ToLower('A'))) // a

// unicode/utf8 — UTF-8 byte processing
s := "Hello, World!"

// Rune (character) count — len() returns byte count
fmt.Println("byte count:", len(s)) // 13
fmt.Println("rune count:", utf8.RuneCountInString(s)) // 13

// Check for valid UTF-8
fmt.Println("valid UTF-8:", utf8.ValidString(s)) // true

// First rune and its byte size
r, size := utf8.DecodeRuneInString(s)
fmt.Printf("first rune: %c, size: %d bytes\n", r, size) // H, 1

// Iterate rune by rune (range automatically decodes UTF-8)
for i, r := range s {
if !unicode.IsLetter(r) && !unicode.IsDigit(r) {
fmt.Printf("position %d: non-letter '%c'\n", i, r)
}
}
}

regexp Package — Regular Expressions

Basic Usage

package main

import (
"fmt"
"regexp"
)

func main() {
// Compile — returns an error (use for user-provided patterns)
re, err := regexp.Compile(`\d+`)
if err != nil {
fmt.Println("regexp error:", err)
return
}

// MustCompile — panics on error (suitable for package-level variables)
digitRe := regexp.MustCompile(`\d+`)

s := "age: 25, score: 98, rank: 3"

// Check for match
fmt.Println(re.MatchString("abc123")) // true
fmt.Println(digitRe.MatchString("abc")) // false

// Find first match
fmt.Println(digitRe.FindString(s)) // "25"
fmt.Println(digitRe.FindStringIndex(s)) // [5 7] — byte indices

// Find all matches
fmt.Println(digitRe.FindAllString(s, -1)) // [25 98 3]
fmt.Println(digitRe.FindAllString(s, 2)) // [25 98] — at most 2

// Replace
result := digitRe.ReplaceAllString(s, "##")
fmt.Println(result) // "age: ##, score: ##, rank: ##"

// Replace with function
result2 := digitRe.ReplaceAllStringFunc(s, func(match string) string {
return "[" + match + "]"
})
fmt.Println(result2) // "age: [25], score: [98], rank: [3]"

// Split
spaceRe := regexp.MustCompile(`\s+`)
fmt.Println(spaceRe.Split("hello world\tfoo", -1)) // [hello world foo]
}

Capture Groups

package main

import (
"fmt"
"regexp"
)

func main() {
// Capture groups — parts enclosed in parentheses
dateRe := regexp.MustCompile(`(\d{4})-(\d{2})-(\d{2})`)

date := "today is 2024-01-15"

// FindStringSubmatch — full match + groups
match := dateRe.FindStringSubmatch(date)
if match != nil {
fmt.Println("full match:", match[0]) // "2024-01-15"
fmt.Println("year:", match[1]) // "2024"
fmt.Println("month:", match[2]) // "01"
fmt.Println("day:", match[3]) // "15"
}

// FindAllStringSubmatch — all matches + groups
text := "start: 2024-01-15, end: 2024-12-31"
allMatches := dateRe.FindAllStringSubmatch(text, -1)
for _, m := range allMatches {
fmt.Printf("date: %s (year: %s, month: %s, day: %s)\n",
m[0], m[1], m[2], m[3])
}

// Named capture groups
namedRe := regexp.MustCompile(`(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})`)
m := namedRe.FindStringSubmatch("2024-01-15")
if m != nil {
names := namedRe.SubexpNames()
for i, name := range names {
if name != "" {
fmt.Printf("%s: %s\n", name, m[i])
}
}
}
}

Real-World Example 1 — Email Validator

package main

import (
"fmt"
"regexp"
"strings"
)

// package-level variables — compile once, reuse forever
var (
emailRe = regexp.MustCompile(
`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`,
)
phoneRe = regexp.MustCompile(`^(\+1|0)\d{9,10}$`)
)

type ValidationResult struct {
Valid bool
Errors []string
}

func validateEmail(email string) ValidationResult {
var errs []string

email = strings.TrimSpace(email)
if email == "" {
errs = append(errs, "email is empty")
return ValidationResult{false, errs}
}

if len(email) > 254 {
errs = append(errs, "email is too long (max 254 characters)")
}

if !emailRe.MatchString(email) {
errs = append(errs, "invalid email format")
}

parts := strings.Split(email, "@")
if len(parts) == 2 {
local := parts[0]
if strings.HasPrefix(local, ".") || strings.HasSuffix(local, ".") {
errs = append(errs, "local part cannot start or end with a dot")
}
if strings.Contains(local, "..") {
errs = append(errs, "local part contains consecutive dots")
}
}

return ValidationResult{len(errs) == 0, errs}
}

func main() {
testEmails := []string{
"user@example.com",
"invalid.email",
"user@.com",
"user..name@example.com",
"valid+tag@domain.co.uk",
"",
"@no-local.com",
}

for _, email := range testEmails {
result := validateEmail(email)
if result.Valid {
fmt.Printf("✓ '%s' — valid\n", email)
} else {
fmt.Printf("✗ '%s' — errors: %s\n", email, strings.Join(result.Errors, ", "))
}
}
}

Real-World Example 2 — Text Parser (Log Analyzer)

package main

import (
"fmt"
"regexp"
"strconv"
"strings"
)

// log pattern: [2024-01-15 10:30:00] [INFO] request_id=abc123 status=200 duration=150ms
var logRe = regexp.MustCompile(
`\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\] \[(\w+)\] (.+)`,
)
var kvRe = regexp.MustCompile(`(\w+)=(\S+)`)

type LogEntry struct {
Timestamp string
Level string
Fields map[string]string
}

func parseLog(line string) (*LogEntry, bool) {
m := logRe.FindStringSubmatch(line)
if m == nil {
return nil, false
}

entry := &LogEntry{
Timestamp: m[1],
Level: m[2],
Fields: make(map[string]string),
}

// parse key-value pairs
kvMatches := kvRe.FindAllStringSubmatch(m[3], -1)
for _, kv := range kvMatches {
entry.Fields[kv[1]] = kv[2]
}

return entry, true
}

type LogStats struct {
TotalRequests int
ErrorCount int
TotalDuration int
StatusCodes map[string]int
}

func analyzeLog(lines []string) LogStats {
stats := LogStats{StatusCodes: make(map[string]int)}

for _, line := range lines {
entry, ok := parseLog(line)
if !ok {
continue
}

if entry.Level == "ERROR" {
stats.ErrorCount++
}

if status, ok := entry.Fields["status"]; ok {
stats.TotalRequests++
stats.StatusCodes[status]++
}

if dur, ok := entry.Fields["duration"]; ok {
dur = strings.TrimSuffix(dur, "ms")
if ms, err := strconv.Atoi(dur); err == nil {
stats.TotalDuration += ms
}
}
}

return stats
}

func main() {
logs := []string{
`[2024-01-15 10:30:00] [INFO] request_id=req-001 status=200 duration=150ms`,
`[2024-01-15 10:30:01] [INFO] request_id=req-002 status=200 duration=89ms`,
`[2024-01-15 10:30:02] [ERROR] request_id=req-003 status=500 duration=3200ms`,
`[2024-01-15 10:30:03] [WARN] request_id=req-004 status=429 duration=12ms`,
`[2024-01-15 10:30:04] [INFO] request_id=req-005 status=201 duration=234ms`,
`[2024-01-15 10:30:05] [ERROR] request_id=req-006 status=503 duration=5000ms`,
`server restarting...`, // parse failure example
}

stats := analyzeLog(logs)

fmt.Printf("=== Log Analysis Results ===\n")
fmt.Printf("total requests: %d\n", stats.TotalRequests)
fmt.Printf("error count: %d\n", stats.ErrorCount)
if stats.TotalRequests > 0 {
avg := stats.TotalDuration / stats.TotalRequests
fmt.Printf("average response time: %dms\n", avg)
}
fmt.Printf("status code distribution:\n")
for code, count := range stats.StatusCodes {
fmt.Printf(" %s: %d requests\n", code, count)
}
}

Package Comparison Summary

PackagePrimary UseKey Functions
stringsString manipulationContains, Split, Join, Replace, Builder
strconvType conversionAtoi, Itoa, ParseFloat, FormatInt
unicodeCharacter classificationIsLetter, IsDigit, ToUpper
unicode/utf8UTF-8 processingRuneCountInString, DecodeRuneInString
regexpRegular expressionsMustCompile, FindAllString, ReplaceAllString

Regular expressions are powerful, but strings is much faster for simple string searches. Use strings.Contains or strings.Index for fixed patterns, and reserve regexp for complex patterns only.