Skip to content

std/string ​

String search, transform, and inspect operations are methods on any string value — no import needed. The character-class helpers below are free functions imported from std/string.

milo
let name = "  Alice  "
let clean = name.trim().toLower()   // "alice"

String methods ​

Built into the language: the checker dispatches these on any string, so they need no import. Indices are byte offsets unless the note says otherwise.

milo
let parts = "a,b,c".split(",")   // ["a", "b", "c"]
match "hello".indexOf("l") {
    Some(i) => print(i),   // 2
    None => print("absent"),
}
string methodNotes
len: i64
isEmpty(): bool
contains(needle: string): bool
startsWith(prefix: string): bool
endsWith(suffix: string): bool
indexOf(needle: string): Option<i64>
indexOfFrom(needle: string, from: i64): Option<i64>
lastIndexOf(needle: string): Option<i64>
charAt(i: i64): stringthe whole character starting at BYTE offset i: charAt(1) of "aéb" is "é"; an offset inside a multibyte character aborts. Iterate with for c in s.codePoints() for characters
substr(start: i64, end: i64): stringBYTE offsets — a bound inside a multibyte character splits it and yields invalid UTF-8, silently
slice(start: i64, end: i64): &stringa view, not a copy — the receiver is frozen while it lives. BYTE offsets, like substr
toLower(): stringASCII only — "AÉB" lowercases to "aÉb"; std/unicode's unicodeLower maps every script
toUpper(): stringASCII only — "aéb" uppercases to "AéB"; std/unicode's unicodeUpper maps every script
trim(): string
trimStart(): string
trimEnd(): string
repeat(n: i64): string
reverse(): stringcode-point aware, unlike charAt/substr — "日本語" reverses to "語本日"
padStart(targetLen: i64, pad: string): stringtargetLen counts characters (code points), so "café" and "cafe" pad to the same column; the pad string is cycled by character
padEnd(targetLen: i64, pad: string): stringtargetLen counts characters, like padStart
replace(old: string, new: string): string
replaceFirst(old: string, new: string): string
split(sep: string): Vec<string>
splitWords(): Vec<string>
splitWhitespace(): Vec<string>
lines(): &string piecesfor-in only — yields views into the receiver, never owned copies
splitView(sep: string): &string piecesfor-in only — yields views into the receiver, never owned copies
codePoints(): i32 code pointsfor-in only — a parser desugar, not a value you can bind
parseInt(): Option<i64>
parseF64(): Option<f64>
push(c: u8)
pushStr(s: &string)
pushInt(n: i64)appends n's decimal digits in place; takes any integer width (u64, i8, ...), no temporary string
clear()empties the string, keeping its capacity
truncate(len: i64)keeps the first len BYTES and the capacity; len past the end is a no-op, a len inside a multibyte character aborts
setByte(i: i64, b: u8)overwrites byte i in place, the write-side twin of s[i]; i outside 0..len aborts. A byte op like s[i]: it does not keep the text valid UTF-8
cstr(): *u8NUL-terminated pointer to the bytes; the string must outlive it and not be written meanwhile. A string held in a local or its field is made the only handle to its buffer first (copy-on-write), so C may write the bytes in place; through a &string or a global they are read-only
clone(): string

Character helpers ​

The ASCII byte classifiers are free functions in std/string. The prelude imports them into every program, so they need no import either. Each takes a u8.

milo
from "std/string" import { asciiIsWhitespace, asciiIsDigit, asciiIsAlpha, asciiIsAlphanumeric }

API reference ​

asciiIsAlpha ​

milo
pub fn asciiIsAlpha(ch: u8): bool

Check if a byte is an ASCII letter.

asciiIsAlphanumeric ​

milo
pub fn asciiIsAlphanumeric(ch: u8): bool

Check if a byte is an ASCII letter or digit.

asciiIsControl ​

milo
pub fn asciiIsControl(ch: u8): bool

asciiIsDigit ​

milo
pub fn asciiIsDigit(ch: u8): bool

Check if a byte is an ASCII digit.

asciiIsHexDigit ​

milo
pub fn asciiIsHexDigit(ch: u8): bool

asciiIsLower ​

milo
pub fn asciiIsLower(ch: u8): bool

asciiIsPrintable ​

milo
pub fn asciiIsPrintable(ch: u8): bool

asciiIsPunctuation ​

milo
pub fn asciiIsPunctuation(ch: u8): bool

asciiIsUpper ​

milo
pub fn asciiIsUpper(ch: u8): bool

asciiIsWhitespace ​

milo
pub fn asciiIsWhitespace(ch: u8): bool

Check if a byte is ASCII whitespace.

asciiToLower ​

milo
pub fn asciiToLower(ch: u8): u8

asciiToUpper ​

milo
pub fn asciiToUpper(ch: u8): u8

strContainsIgnoreCase ​

milo
pub fn strContainsIgnoreCase(haystack: &string, needle: &string): bool

True if needle occurs anywhere in haystack, ignoring ASCII case.

strIndexOfFromIgnoreCase ​

milo
pub fn strIndexOfFromIgnoreCase(haystack: &string, needle: &string, pos: i64): i64

First occurrence of needle in haystack at or after pos, comparing ASCII letters case-insensitively, or -1.

Same memchr skip as strIndexOfFrom, with two differences that both come from case folding:

  • The scan anchors on the first byte of the needle that is NOT a letter. That byte has one spelling, so memchr runs once instead of once per case, and a digit or punctuation byte is rarer in text than a letter, so each call rejects far more. Searching "uint256" case-insensitively anchors on '2', not 'u'. A needle that is all letters falls back to the two-pass scan on byte 0.
  • When two passes ARE running, only the one whose hit was just consumed is re-run. Refreshing both after every failed verify re-scans the rest of the buffer per candidate, which is quadratic in candidate count.

The verify is a byte loop rather than strncasecmp: the MSVC CRT spells that _strnicmp, and this file is in the prelude of every program on every platform. It only runs on candidate positions, which memchr has already made rare.