Strings
UTF-8 text with powerful built-in methods for manipulation, searching, and transformation.
Creating Strings
String Literals
Create strings using double quotes. Supports escape sequences for special characters.
# Basic string literals
greeting = "Hello, World!"
name = "Alice"
empty = ""
# Escape sequences
newline = "Line 1\nLine 2"
tab = "Column1\tColumn2"
quote = "She said \"Hello\""
backslash = "Path: C:\\Users\\name"
# Unicode characters
emoji = "Hello \u{1F44B}"; # Hello with wave emoji
unicode = "\u{00E9}"; # e with accent
Raw Strings
Raw strings don't process escape sequences - useful for paths and regex patterns.
# Raw strings - no escape processing
path = r"C:\Users\name\Documents"
regex = r"\d+\.\d+"
# Compare with regular strings
regular = "C:\\Users\\name"; # Need double backslashes
raw = r"C:\Users\name"; # Single backslashes work
Multiline Strings
Use triple quotes or bracket syntax for multiline strings. Preserves newlines and indentation.
# Triple quote syntax (recommended)
poem = """The fog comes
on little cat feet.
It sits looking
over harbor and city."""
# Bracket syntax [[ ]]
story = [[Once upon
a time in
the wild west.]]
# HTML template example
html = """
Welcome
This is a paragraph.
"""
# SQL query example
query = """SELECT id, name, email
FROM users
WHERE active = true
ORDER BY created_at DESC""";
Note: Both syntaxes create raw strings (no escape processing). The leading newline and indentation are preserved as-is. Use .trim to remove leading/trailing whitespace if needed.
Concatenation
Join strings together using the + operator. Non-string values are automatically converted.
# Basic concatenation
greeting = "Hello, " + "World!"; # "Hello, World!"
# Auto-conversion of other types
message = "Value: " + 42; # "Value: 42"
result = "Pi is " + 3.14159; # "Pi is 3.14159"
status = "Active: " + true; # "Active: true"
# Building strings
name = "Alice"
age = 30
intro = "My name is " + name + " and I am " + age.to_s + " years old."
String Interpolation
Embed expressions directly in strings using #{expression} syntax.
# Basic variable interpolation
name = "World"
greeting = "Hello #{name}!"; # "Hello World!"
# Arithmetic expressions
a = 2
b = 3
result = "Sum is #{a + b}"; # "Sum is 5"
# Multiple interpolations
first = "John"
last = "Doe"
full = "#{first} #{last}"; # "John Doe"
# Method calls
text = "hello"
upper = "Upper: #{text.upper()}"; # "Upper: HELLO"
# Array access
items = ["Alice", "Bob"]
first_item = "First: #{items[0]}"; # "First: Alice"
# Hash access
person = {"name": "Charlie"}
person_name = "Name: #{person["name"]}"; # "Name: Charlie"
Type Annotations
Optionally specify string types for clarity and type safety.
# Typed strings
let name: String = "Alice"
let message: String = ""
# String arrays
let names: String[] = ["Alice", "Bob", "Charlie"]
# Function parameters
def greet(name: String) -> String
"Hello, " + name + "!"
end
Accessing Characters
Index Access
Access individual characters using zero-based indexing. Negative indices count from the end.
str = "Hello, World!"
# Zero-based indexing
print(str[0]); # "H" (first character)
print(str[7]); # "W"
# Negative indexing
print(str[-1]); # "!" (last character)
print(str[-2]); # "d" (second to last)
# Out of bounds returns null
print(str[100]); # null
String Immutability
Strings are immutable - methods return new strings rather than modifying the original.
original = "hello"
upper = original.upcase
print(original) # "hello" (unchanged)
print(upper) # "HELLO" (new string)
# To "modify", reassign the variable
text = "hello"
text = text.upcase
print(text) # "HELLO"
String Methods
All string methods are called with dot notation on string values.
.length / .len / .size
Returns the number of characters in the string.
"hello".length # 5
"hello".size # 5
"".len # 0
.bytesize / .bytes
bytesize returns the number of bytes (UTF-8). bytes returns the byte values as an array.
"hello".bytesize # 5
"hello".bytes # [104, 101, 108, 108, 111]
.empty? / .blank? / .present?
Check if string is empty, blank (empty or only whitespace), or present (not blank).
"".empty? # true
"hello".empty? # false
" ".empty? # false
"".blank? # true
" ".blank? # true (only whitespace)
"hello".blank? # false
"hello".present? # true
"".present? # false
" ".present? # false
.inspect / .class / .is_a?(type) / .nil?
Introspection methods for debugging and type checking.
"hello".inspect # "\"hello\""
"hello".class # "String"
"hello".is_a?("String") # true
"hello".nil? # false
.upcase / .downcase / .capitalize / .swapcase
Case conversion methods. uppercase and lowercase are aliases for upcase and downcase.
"hello".upcase # "HELLO"
"HELLO".downcase # "hello"
"hello".capitalize # "Hello"
"Hello World".swapcase # "hELLO wORLD"
# Aliases
"hello".uppercase # "HELLO"
"HELLO".lowercase # "hello"
.slugify
Converts the string to a URL-friendly slug: lowercases, ASCII-folds common Latin/Slavic accents (à→a, é→e, ç→c, œ→oe, æ→ae, ß→ss, …), replaces any remaining non-[a-z0-9] with -, collapses runs, and trims leading/trailing hyphens.
"Hello World!".slugify # "hello-world"
"Café & Restaurant".slugify # "cafe-restaurant"
"Crème brûlée".slugify # "creme-brulee"
" multiple spaces ".slugify # "multiple-spaces"
.camelize / .camelize(true)
Converts snake_case or kebab-case input to camel case. Default is lower-camel (fooBar); pass true for upper-camel / Pascal case (FooBar). Leading and consecutive separators are collapsed, and internal capitals are preserved so already-camelized input is idempotent.
"foo_bar".camelize # "fooBar"
"foo-bar-baz".camelize # "fooBarBaz"
"foo_bar".camelize(true) # "FooBar" (PascalCase)
"FooBar".camelize # "fooBar" (lowercases first char)
"fooBar".camelize # "fooBar" (idempotent)
"_foo__bar_".camelize # "fooBar" (collapses leading/consecutive separators)
.contains(substr) / .includes?(substr)
Checks if the string contains a substring. includes? is an alias.
"hello world".contains("world") # true
"hello world".contains("foo") # false
"hello".includes?("ell") # true
.starts_with(prefix) / .ends_with(suffix)
Check if a string starts or ends with a given substring. Also available as starts_with? and ends_with?.
"https://example.com".starts_with("https://") # true
"report.pdf".ends_with(".pdf") # true
# With ? suffix (alias)
"hello".starts_with?("he") # true
"hello".ends_with?("lo") # true
.index_of(substr)
Returns the index of the first occurrence of a substring, or -1 if not found.
"hello world".index_of("world") # 6
"hello world".index_of("o") # 4
"hello world".index_of("foo") # -1
.count(substr)
Counts the number of occurrences of a substring.
"hello".count("l") # 2
"banana".count("ana") # 1
"hello".count("x") # 0
.match(pattern) / .scan(pattern)
match returns the first regex match. scan returns all matches as an array.
"hello 42 world 7".match(r"\d+") # "42"
"hello 42 world 7".scan(r"\d+") # ["42", "7"]
.trim / .strip / .lstrip / .rstrip / .chomp
Whitespace removal. trim (alias strip) removes both sides, lstrip/rstrip remove left/right only, chomp removes trailing newlines.
" hello ".trim # "hello"
" hello ".strip # "hello" (alias)
" hello ".lstrip # "hello "
" hello ".rstrip # " hello"
"hello\n".chomp # "hello"
.squeeze
Collapses runs of repeated characters into a single character.
"aaabbbccc".squeeze # "abc"
"hello world".squeeze # "helo world"
.reverse
Returns a new string with characters in reverse order.
"hello".reverse # "olleh"
"12345".reverse # "54321"
.succ / .next
Returns the successor of the string. Increments the last alphanumeric character, carrying to the left and prepending a new character when all positions wrap. Useful for generating sequential IDs, version numbers, or URL slugs.
"a".succ # "b"
"z".succ # "aa"
"zz".succ # "aaa"
"9".succ # "10"
"99".succ # "100"
"a9".succ # "b0"
"abc".succ # "abd"
"abz".succ # "aca"
# .next is an alias
"a".next # "b"
.replace(search, replacement) / .gsub(pattern, replacement) / .replace_all(pattern, replacement) / .sub(pattern, replacement)
replace replaces all occurrences. gsub (alias: replace_all) replaces all regex matches. sub replaces first regex match only.
"hello world".replace("world", "Soli") # "hello Soli"
"aaa".replace("a", "b") # "bbb"
"hello 42 world 7".gsub(r"\d+", "X") # "hello X world X"
"hello 42 world 7".replace_all(r"\d+", "X") # "hello X world X" (alias for gsub)
"hello 42 world 7".sub(r"\d+", "X") # "hello X world 7"
.tr(from, to)
Translates characters: replaces each character in from with the corresponding character in to.
"hello".tr("aeiou", "*") # "h*ll*"
"hello".tr("el", "ip") # "hippo"
.delete(chars) / .delete_prefix(prefix) / .delete_suffix(suffix)
delete removes all occurrences of specified characters. delete_prefix/delete_suffix remove a specific prefix or suffix.
"hello".delete("lo") # "he"
"Hello, World!".delete_prefix("Hello, ") # "World!"
"report.pdf".delete_suffix(".pdf") # "report"
.insert(index, string)
Inserts a string at the specified index position.
"helo".insert(3, "l") # "hello"
"world".insert(0, "hello ") # "hello world"
.truncate(length)
Truncates the string to the specified length.
"hello world".truncate(5) # "hello"
"hi".truncate(10) # "hi"
.substring(start, end?)
Extracts a portion of the string from start index to end index (exclusive).
"hello".substring(0, 2) # "he"
"hello".substring(2) # "llo"
"hello".substring(-3) # "llo"
.split([separator]) / .join(separator)
split splits a string into an array. The separator is optional and defaults to " " (a single space). join joins characters back with a separator.
"a,b,c".split(",") # ["a", "b", "c"]
"hello world".split(" ") # ["hello", "world"]
"hello world".split # ["hello", "world"] (no parens — sep defaults to " ")
"hello world".split() # ["hello", "world"]
"hello".split("") # ["h", "e", "l", "l", "o"]
.chars / .lines
chars splits into individual characters. lines splits by newlines.
"hello".chars # ["h", "e", "l", "l", "o"]
"line1\nline2\nline3".lines # ["line1", "line2", "line3"]
for (c in "Soli".chars)
print(c)
end
.partition(separator) / .rpartition(separator)
Splits into 3 parts: [before, separator, after]. rpartition searches from the right.
"hello-world-test".partition("-") # ["hello", "-", "world-test"]
"hello-world-test".rpartition("-") # ["hello-world", "-", "test"]
.lpad(width, char?) / .rpad(width, char?) / .center(width, char?)
Pad a string to a specified width. ljust/rjust are aliases for rpad/lpad.
"42".lpad(5, "0") # "00042"
"hi".rpad(5, ".") # "hi..."
"hi".center(7, "-") # "--hi---"
# Default pad char is space
"5".lpad(3) # " 5"
"hi".rpad(5) # "hi "
.ord / .chr / .hex / .oct
Character encoding conversions. ord returns the Unicode code point, chr returns the character, hex/oct convert to hexadecimal/octal representation.
"A".ord # 65
"A".hex # "41"
"A".oct # "101"
.to_i / .to_f / .to_s / .to_string / .to_sym / .parse_json
Type conversion methods. to_int/to_float are aliases for to_i/to_f.
"42".to_i # 42
"3.14".to_f # 3.14
42.to_s # "42"
[1,2,3].to_string # "[1, 2, 3]"
# Convert to symbol
"name".to_sym # :name
"hello".to_sym # :hello
# Parse JSON
'{"name":"Alice"}'.parse_json # {"name": "Alice"}
"[1,2,3]".parse_json # [1, 2, 3]
"not json".parse_json # {}
.casecmp(other) / .casecmp?(other)
Case-insensitive comparison. casecmp returns -1, 0, or 1 (like <=>). casecmp? returns true if equal ignoring case.
"hello".casecmp("HELLO") # 0
"apple".casecmp("banana") # -1
"banana".casecmp("apple") # 1
"hello".casecmp?("HELLO") # true
"hello".casecmp?("world") # false
.prepend(other)
Returns a new string with other prepended to the front. Strings are immutable so the original is unchanged.
"world".prepend("hello ") # "hello world"
"bar".prepend("foo") # "foobar"
# Equivalent to concatenation
"bar".prepend("foo") # "foobar"
"foo" + "bar" # "foobar"
.chop
Removes the last character from the string. Unlike chomp which only removes trailing newlines, chop removes any last character.
"hello".chop # "hell"
"world!".chop # "world"
"a".chop # ""
"".chop # ""
"hello\n".chop # "hello"
"hello\r\n".chop # "hello\r" (removes \n only)
.ascii_only?
Returns true if the string contains only ASCII characters (bytes 0-127).
"hello".ascii_only? # true
"café".ascii_only? # false
"".ascii_only? # true
HTML Functions
html_escape(string)
Escape HTML special characters to prevent XSS attacks.
html_escape("<div class='test'>Hello</div>")
# "<div class='test'>Hello</div>"
html_escape("Tom & Jerry")
# "Tom & Jerry"
html_escape("5 > 3 && 3 < 5")
# "5 > 3 && 3 < 5"
html_unescape(string)
Convert HTML entities back to their original characters.
html_unescape("<div>")
# "<div>"
html_unescape("Tom & Jerry")
# "Tom & Jerry"
sanitize_html(string)
Remove dangerous HTML tags while keeping safe formatting tags.
sanitize_html("<b>Bold</b> <style>body{color:red}</style>")
# "<b>Bold</b> "
sanitize_html("<p>Hello</p><object data='file.swf'></object>")
# "<p>Hello</p>"
URL Functions
url_encode(value)
Strict RFC 3986 component encoding. Percent-encodes every reserved character (/, ?, &, =, #, space, …) so the result is safe to splice into any URL component — query value, path segment, or fragment. Scalars (Int / Float / Bool) are stringified first; null becomes the empty string.
url_encode("hello world") # "hello%20world"
url_encode("a/b?c=d") # "a%2Fb%3Fc%3Dd"
url_encode("café") # "caf%C3%A9"
# Building a query string by hand
url = "/results?q=" + url_encode("search " + str(page))
url_decode(string)
Form-style decode: + becomes space, %xx becomes the corresponding byte. Invalid %xx sequences pass through literally; raises only when the percent-decoded bytes are not valid UTF-8. req["query"] and req["form"] are already decoded — reach for url_decode when you receive a URL through some other channel (a header, a webhook body, a stored URL string).
url_decode("hello%20world") # "hello world"
url_decode("hello+world") # "hello world"
url_decode("a%2Fb%3Fc%3Dd") # "a/b?c=d"
url_decode("caf%C3%A9") # "café"
# Roundtrip
url_decode(url_encode("a/b?c=d")) # "a/b?c=d"
# Fallback for input you don't trust
safe = url_decode(raw) rescue raw
Common Patterns
Parsing and Formatting
# Parse CSV line
csv_line = "Alice,30,Engineer"
fields = csv_line.split(",")
name = fields[0] # "Alice"
age = fields[1].to_i # 30
job = fields[2] # "Engineer"
# Parse URL query string
def parse_query(query: String) -> Hash
result = {}
pairs = query.split("&")
for (pair in pairs)
parts = pair.split("=")
if (parts.length == 2)
result[parts[0]] = parts[1]
end
end
result
end
params = parse_query("name=Alice&age=30")
# {"name": "Alice", "age": "30"}
Validation Helpers
# Basic email validation
def is_valid_email(email: String) -> Bool
email.contains("@") && email.contains(".")
end
# Check minimum length
def has_min_length(s: String, min: Int) -> Bool
s.length >= min
end
# Check if string is numeric
def is_numeric(s: String) -> Bool
if (s.empty?)
return false
end
for (c in s.chars)
if (!"0123456789".contains(c))
return false
end
end
true
end
# Validate username
def is_valid_username(username: String) -> Bool
trimmed = username.trim
trimmed.length >= 3 && trimmed.length <= 20
end
Template Strings
# Simple template replacement
def template(text: String, vars: Hash) -> String
result = text
for (pair in entries(vars))
key = pair[0]
value = pair[1]
result = result.replace("{{" + key + "}}", value.to_string)
end
result
end
tmpl = "Hello, {{name}}! You have {{count}} messages."
output = template(tmpl, {"name": "Alice", "count": 5})
# "Hello, Alice! You have 5 messages."
# Build HTML safely
def build_link(url: String, text: String) -> String
"" + html_escape(text) + ""
end