doc/string.md
XGo provides powerful and flexible string handling capabilities. Strings are sequences of characters used to represent text, and they are one of the most commonly used data types in programming.
XGo provides multiple ways to represent strings, from simple literals to complex Unicode characters.
In XGo, you can create strings using double quotes:
name := "Bob"
message := "Hello, World!"
empty := ""
XGo also supports raw string literals using backticks (`). Raw strings treat backslashes and other special characters literally, making them ideal for regular expressions, file paths, and multi-line text:
// Raw strings ignore escape sequences
path := `C:\Users\Bob\Documents` // Backslashes are literal
regex := `\d+\.\d+` // No need to escape backslashes
// Multi-line raw strings
multiline := `Line 1
Line 2
Line 3`
// JSON or code snippets
json := `{
"name": "Alice",
"age": 30
}`
// SQL queries
query := `SELECT * FROM users
WHERE age > 18
ORDER BY name`
Key differences between double-quoted and raw strings:
| Feature | Double-quoted "..." | Raw (backtick) `...` |
|---|---|---|
| Escape sequences | Processed (\n, \t, etc.) | Literal (ignored) |
| Multi-line | Requires \n | Natural line breaks |
| Backslashes | Must escape \\ | Literal \ |
| Interpolation | Supported ${...} | Not supported |
| Use case | General strings, interpolation | Paths, regex, multi-line text |
// Comparison example
escaped := "Line 1\nLine 2" // Two lines when printed
raw := `Line 1\nLine 2` // Literal \n characters
echo escaped
// Output:
// Line 1
// Line 2
echo raw
// Output:
// Line 1\nLine 2
XGo supports various escape sequences for special characters:
// Common escape sequences
newline := "Line 1\nLine 2" // Newline
tab := "Column1\tColumn2" // Tab
quote := "She said \"Hello\"" // Double quote
backslash := "Path: C:\\files" // Backslash
// Octal escape notation \### where # is an octal digit
octalChar := "\141a" // aa
// Unicode can be specified as \u#### where # is a hex digit
// It will be converted internally to its UTF-8 representation
star := "\u2605" // ★
heart := "\u2665" // ♥
| Sequence | Description | Example |
|---|---|---|
\n | Newline | "Line 1\nLine 2" |
\t | Tab | "Name:\tAlice" |
\\ | Backslash | "C:\\path" |
\" | Double quote | "He said \"Hi\"" |
\### | Octal character | "\141" (a) |
\u#### | Unicode character | "\u2605" (★) |
String values are immutable in XGo. Once created, you cannot modify individual characters:
s := "hello 🌎"
s[0] = `H` // Error: not allowed
To modify a string, you must create a new one:
s := "hello"
s = "Hello" // OK: assigning a new string
s = s + " world" // OK: creating a new concatenated string
Indexing a string returns a byte value (not a rune or another string):
name := "Bob"
echo name[0] // 66 (ASCII value of 'B')
echo name[1] // 111 (ASCII value of 'o')
echo name[2] // 98 (ASCII value of 'b')
Warning: Indexing into multi-byte characters (like Chinese characters or emojis) will return individual bytes, which may not represent a complete character.
You can extract substrings using slice notation:
name := "Bob"
echo name[1:3] // ob (from index 1 to 3, exclusive)
echo name[:2] // Bo (from start to index 2)
echo name[2:] // b (from index 2 to end)
s := "Hello, World!"
echo s[0:5] // Hello
echo s[:5] // Hello (start defaults to 0)
echo s[7:] // World! (end defaults to string length)
Slicing syntax:
s[start:end] - from index start to end (exclusive)s[:end] - from beginning to ends[start:] - from start to end of stringStrings can be easily converted to integers:
s := "12"
a, err := s.int // Returns value and error (safe conversion)
b := s.int! // Panics if s isn't a valid integer (unsafe conversion)
// Example with error handling
if num, err := s.int; err == nil {
echo "Valid number:", num
} else {
echo "Invalid number"
}
To convert other types to strings, use the .string property:
age := 10
ageStr := age.string
echo "age = " + ageStr // age = 10
pi := 3.14159
piStr := pi.string
echo "π = " + piStr // π = 3.14159
Use the + operator to concatenate strings:
name := "Bob"
bobby := name + "by"
echo bobby // Bobby
s := "Hello "
s += "world"
echo s // Hello world
// Multiple concatenations
greeting := "Hello" + " " + "World" + "!"
echo greeting // Hello World!
XGo operators require values of the same type on both sides. You cannot concatenate an integer directly to a string:
age := 10
echo "age = " + age // Error: not allowed
You must convert age to a string first:
age := 10
echo "age = " + age.string // age = 10
XGo supports string interpolation using ${expression} syntax, which provides a cleaner alternative to concatenation:
age := 10
echo "age = ${age}" // age = 10
name := "Alice"
greeting := "Hello, ${name}!"
echo greeting // Hello, Alice!
You can use any expression inside ${...}:
// Arithmetic expressions
x := 5
y := 3
echo "${x} + ${y} = ${x + y}" // 5 + 3 = 8
// Function calls and method calls
name := "bob"
echo "Hello, ${name.toUpper}!" // Hello, BOB!
// Complex example
host := "example.com"
page := 0
limit := 20
url := "https://${host}/items?page=${page+1}&limit=${limit}"
echo url // https://example.com/items?page=1&limit=20
To include a literal $ in a string, use $$:
echo "Price: $$50" // Price: $50
echo "Total: $$${100 + 50}" // Total: $150
XGo provides built-in methods for common string operations. These methods do not modify the original string (strings are immutable) but return new strings.
You can get the length of a string using the len method:
name := "Bob"
echo name.len // 3
chinese := "你好"
echo chinese.len // 6 (bytes, not characters - each Chinese character is 3 bytes in UTF-8)
Important: len returns the number of bytes, not the number of characters. For strings containing non-ASCII characters (like Chinese, emojis), the byte length will be larger than the character count.
// Convert to uppercase
echo "Hello".toUpper // HELLO
echo "hello world".toUpper // HELLO WORLD
// Convert to lowercase
echo "Hello".toLower // hello
echo "HELLO WORLD".toLower // hello world
// Capitalize first letter of each word
echo "hello world".capitalize // Hello World
echo "the quick brown fox".capitalize // The Quick Brown Fox
// Repeat a string n times
echo "XGo".repeat(3) // XGoXGoXGo
echo "Ha".repeat(5) // HaHaHaHaHa
echo "-".repeat(10) // ----------
// Useful for formatting
separator := "=".repeat(40)
echo separator
echo "Title"
echo separator
// Replace all occurrences
echo "Hello".replaceAll("l", "L") // HeLLo
echo "banana".replaceAll("a", "o") // bonono
// Practical example
text := "The quick brown fox"
censored := text.replaceAll("fox", "***")
echo censored // The quick brown ***
Join a list of strings into a single string with a separator:
// Join with comma
fruits := ["apple", "banana", "cherry"]
echo fruits.join(",") // apple,banana,cherry
// Join with space
words := ["Hello", "World"]
echo words.join(" ") // Hello World
// Join without separator
letters := ["H", "e", "l", "l", "o"]
echo letters.join("") // Hello
// Practical example with newlines
lines := ["Line 1", "Line 2", "Line 3"]
text := lines.join("\n")
echo text
// Output:
// Line 1
// Line 2
// Line 3
Split a string into a list of substrings using a separator:
// Split by delimiter
subjects := "Math-English-Science-History"
subjectList := subjects.split("-")
echo subjectList // [Math English Science History]
// Split by space
sentence := "The quick brown fox"
words := sentence.split(" ")
echo words // [The quick brown fox]
// Split CSV data
csv := "Alice,30,Engineer"
fields := csv.split(",")
echo fields // [Alice 30 Engineer]
// Process split results
for field in fields {
echo "Field:", field
}
In XGo, strings can be traversed by character (rune) or by byte. Understanding the difference is crucial when working with non-ASCII characters.
len returns byte count, not character countUse for in loop to iterate over characters (runes):
// English text (1 byte per character)
s := "Hello"
for c in s {
echo c
}
// Output:
// H
// e
// l
// l
// o
// Mixed text with Chinese characters
s := "你好XGo"
for c in s {
echo c
}
// Output:
// 你
// 好
// X
// G
// o
Use traditional index-based loop to iterate over bytes:
s := "Hello"
for i := 0; i < len(s); i++ {
echo s[i] // Prints byte values: 72, 101, 108, 108, 111
}
// With non-ASCII characters
s := "你好XGo"
for i := 0; i < len(s); i++ {
echo s[i]
}
// Outputs byte values (each Chinese character is 3 bytes)
// For '你': 228, 189, 160
// For '好': 229, 165, 189
// For 'X': 88
// For 'G': 71
// For 'o': 111
⚠️ Important Warnings:
len() returns bytes, not character countfor c in s instead of index-based loops// Example: Chinese characters
s := "你好"
// WRONG: This returns byte count, not character count
echo s.len // 6 (bytes)
// WRONG: This returns part of a character
echo s[0] // 228 (first byte of '你')
// CORRECT: Count characters
count := 0
for _ in s {
count++
}
echo count // 2 (characters)
// CORRECT: Process characters
for char in s {
echo char // Prints: 你, then 好
}
// Process mixed text (need character iteration)
mixed := "Hello你好"
echo "Byte length:", mixed.len // 11 (5 ASCII + 6 for Chinese)
charCount := 0
for _ in mixed {
charCount++
}
echo "Character count:", charCount // 7
// Extract characters correctly
for i, char in mixed {
echo "Character ${i}: ${char}"
}
// Output:
// Character 0: H
// Character 1: e
// Character 2: l
// Character 3: l
// Character 4: o
// Character 5: 你
// Character 8: 好
// Check if string is a valid integer
input := "12345"
if num, err := input.int; err == nil {
echo "Valid number:", num
} else {
echo "Invalid number"
}
// Check string length
username := "alice"
if username.len < 3 {
echo "Username too short"
} else if username.len > 20 {
echo "Username too long"
} else {
echo "Username OK"
}
// Build formatted strings
name := "Alice"
age := 30
city := "New York"
// Using interpolation
profile := "Name: ${name}, Age: ${age}, City: ${city}"
echo profile
// Building multi-line strings
header := "=".repeat(40)
title := "User Profile"
content := "${header}\n${title}\n${header}\nName: ${name}\nAge: ${age}\nCity: ${city}"
echo content
// Parse CSV data
csv := "Alice,30,Engineer,New York"
fields := csv.split(",")
name := fields[0]
age := fields[1].int!
job := fields[2]
city := fields[3]
echo "Name: ${name}, Age: ${age}, Job: ${job}, City: ${city}"
// Parse key-value pairs
config := "host=localhost;port=8080;debug=true"
pairs := config.split(";")
settings := {}
for pair in pairs {
parts := pair.split("=")
key := parts[0]
value := parts[1]
settings[key] = value
}
echo settings // map[host:localhost port:8080 debug:true]
// Email template
func generateEmail(name, action, link string) string {
return "Hello ${name},\n\nPlease click the link below to ${action}:\n${link}\n\nBest regards,\nThe Team"
}
email := generateEmail("Alice", "verify your email", "https://example.com/verify")
echo email
// Word count
text := "The quick brown fox jumps over the lazy dog"
words := text.split(" ")
echo "Word count:", words.len
// Capitalize each word
capitalized := []
for word in words {
capitalized = append(capitalized, word.capitalize)
}
result := capitalized.join(" ")
echo result // The Quick Brown Fox Jumps Over The Lazy Dog
// Remove extra spaces
messyText := " Too many spaces "
cleaned := [s for s in messyText.split(" ") if s != ""].join(" ")
echo cleaned // Too many spaces
// Building a URL with query parameters
func buildURL(base string, params map[string]any) string {
if params.len == 0 {
return base
}
queryParts := []
for key, value in params {
queryParts = append(queryParts, "${key}=${value}")
}
return "${base}?${queryParts.join("&")}"
}
url := buildURL("https://api.example.com/search", {
"q": "xgo",
"page": 1,
"limit": 20,
})
echo url // https://api.example.com/search?q=xgo&page=1&limit=20
// Building a report
func buildReport(title string, items []string) string {
separator := "=".repeat(50)
header := "${separator}\n${title}\n${separator}"
itemList := []
for i, item in items {
itemList = append(itemList, "${i+1}. ${item}")
}
return "${header}\n${itemList.join("\n")}"
}
report := buildReport("Task List", ["Review code", "Write tests", "Update docs"])
echo report
"${expr}") instead of concatenation for better readability.string method to convert other types to stringsfor c in s) when processing text with non-ASCII characters.toUpper, .toLower, etc.) for case-insensitive operationsAvoid excessive concatenation in loops: Build string slices and join them instead
// Less efficient
result := ""
for i := 0; i < 1000; i++ {
result += "item${i},"
}
// More efficient
parts := []
for i := 0; i < 1000; i++ {
parts = append(parts, "item${i}")
}
result := parts.join(",")
Use string interpolation: It's more efficient than multiple concatenations
// Less efficient
message := "Hello, " + name + "! You are " + age.string + " years old."
// More efficient
message := "Hello, ${name}! You are ${age} years old."
Reuse string slices: When splitting strings multiple times, consider reusing slices
Consider byte operations: For performance-critical ASCII-only operations, byte-level processing can be faster
Preallocate when building large strings: If you know the approximate size, preallocate capacity