Browse documentation
Data structures

String

Immutable Unicode text, scalar indexing, literal matching, full default Unicode casing, composition, and time and space complexity.

String is the built in immutable text value. It requires no import, stores UTF-8, and provides Unicode aware indexing, literal search, casing, and text composition. Every operation returns a new value or a scalar result; the original text is unchanged.

Declaration and conversion

aner
let title: String = "Aner research"
let measurement = String(42)
let enabled = String(true)
let combined = title + ": " + measurement
print(combined)

String(value) accepts an Int64, Float64, Bool, or String. Conversion is explicit; concatenation accepts two Strings and does not convert numbers automatically. Booleans become "true" or "false". Int64 conversion preserves its exact decimal value. Float64 conversion requires a finite value and uses a locale independent shortest round trip representation; a whole valued float can omit a decimal point. This conversion is not a dataset serialization format. Objects, collections, null, credentials, and opaque native handles cannot be converted through this constructor.

A let binding cannot be reassigned. A var binding can be assigned a new String, without changing any previously assigned value. Methods do not mutate either binding.

Double quoted literals support \", \\, \n, \r, and \t. Literal Unicode characters are written directly in the UTF-8 source. Unescaped line breaks and ASCII control characters below U+0020 are rejected. Interpolation, raw literals, and Unicode escape syntax are not implemented.

Unicode and indexing contract

len(), get(index), slice(start, end), and the result of find(needle) use Unicode scalar values, rather than encoded bytes or displayed grapheme clusters. Indices have type Int64, start at zero, and must be nonnegative.

aner
let text = "café 🧪"
print(text.len())       // 6 Unicode scalar values
print(text.byte_len())  // 10 UTF-8 bytes
print(text.get(3))      // é
print(text.slice(0, 4)) // café
print(text.find("🧪"))  // 5

get(index) returns a String containing exactly one Unicode scalar value and requires index < len(). There is no separate Char datatype. slice(start, end) uses a half open interval and requires 0 <= start <= end <= len(); equal endpoints return an empty String. Negative indices never count from the end.

A combining mark counts separately from its base letter. A displayed emoji sequence can also contain several scalar values. These methods do not provide grapheme cluster editing. No method silently normalizes text: composed and decomposed forms remain different values unless their encoded text is equal.

The native text operations validate UTF-8 before processing. Malformed input produces a runtime error rather than replacement characters, partial scalar values, or byte based fallback.

Search, splitting, and composition

Search is case sensitive and matches literal text. It does not interpret regular expressions or apply locale collation.

  • find(needle) returns the first scalar index, or -1 when absent. An empty needle returns 0.
  • contains, starts_with, and ends_with accept empty search text and return true for it.
  • count(needle) counts nonoverlapping matches and requires a nonempty needle.
  • split(separator) requires a nonempty literal separator and preserves empty fields, including leading and trailing fields. Splitting an empty String yields one empty field.
  • replace(old, new) requires nonempty old and replaces all nonoverlapping occurrences. Inserted replacement text is not searched again.
  • separator.join(parts) accepts a List whose every element is a String. Mixed Lists, null elements, and opaque values are rejected before producing a result. An empty List produces an empty String.
aner
let fields = "sample,,control,".split(",")
print(fields)                // List("sample", "", "control", "")
print(" | ".join(fields))    // sample |  | control |
print("banana".count("ana")) // 1
print("banana".replace("ana", "X")) // bXna

repeat(count) requires a nonnegative Int64. Zero repetitions produce an empty String. concat(other) and + concatenate two Strings with the same validation and result limits. compare(other) returns -1, 0, or 1 using case sensitive ordinal ordering of Unicode scalar values. Valid UTF-8 preserves this ordering under byte comparison. There is no locale aware or natural number ordering.

Whitespace and casing

trim(), trim_start(), and trim_end() remove Unicode White_Space characters from the selected ends. They preserve internal whitespace. The property follows the bundled Unicode 17.0.0 Character Database; it does not treat every invisible formatting character as whitespace.

lower() and upper() apply full default Unicode case mappings, including length changing mappings and the context sensitive final sigma rule. casefold() applies full default case folding for caseless comparison. These operations use bundled Unicode 17.0.0 full case mappings and case folding data, and do not depend on the operating system's locale. They provide no language specific tailoring, locale parameter, or normalization.

aner
print("  Straße  ".trim().upper()) // STRASSE
print("Straße".casefold() == "STRASSE".casefold()) // true

Case folding can change the text and its length. It does not make canonically equivalent composed and decomposed spellings equal. Preserve the original String when the displayed spelling is needed.

Methods

Arguments may be positional or named with the following labels. Receiver methods require a statically known String; use cast<String>(value) to recover that type from Any after inspecting it.

MethodResultContract
byte_len()Int64Count UTF-8 bytes after validation.
casefold()StringApply full default Unicode case folding.
compare(other: String)Int64Return ordinal comparison as −1, 0, or 1.
concat(other: String)StringAppend the other text.
contains(needle: String)BoolTest whether literal text occurs.
count(needle: String)Int64Count nonoverlapping occurrences of a nonempty needle.
ends_with(suffix: String)BoolTest a literal suffix.
find(needle: String)Int64Return the first scalar index, or −1.
get(index: Int64)StringReturn exactly one scalar value at a valid index.
is_empty()BoolTest whether validated text is empty.
join(parts: List)StringJoin only String elements, using this receiver as separator.
len()Int64Count Unicode scalar values.
lower()StringApply full default lowercase mapping.
repeat(count: Int64)StringRepeat the text a nonnegative number of times.
replace(old: String, new: String)StringReplace all nonoverlapping occurrences of nonempty old text.
slice(start: Int64, end: Int64)StringCopy the scalar interval [start, end).
split(separator: String)ListSplit on a nonempty literal separator, preserving empty fields.
starts_with(prefix: String)BoolTest a literal prefix.
trim()StringRemove White_Space at both ends.
trim_end()StringRemove White_Space at the end.
trim_start()StringRemove White_Space at the start.
upper()StringApply full default uppercase mapping.

Time and space complexity

Let n be receiver UTF-8 bytes, m the other String or search pattern bytes, s replacement bytes, r result bytes, k result fields or joined parts, and p the sum of joined part bytes. Space includes newly owned results and auxiliary buffers beyond evaluated arguments.

OperationTimeAdditional space
len, byte_len, is_emptyO(n), including UTF-8 validationO(1).
getO(n), including validation and scalar lookupO(1): one scalar occupies at most four bytes.
slice, trim, trim_start, trim_endO(n + r)O(r) result; O(1) auxiliary.
find, contains, countO(n + m)O(m) search prefix buffer; O(1) scalar result.
starts_with, ends_with, compareO(n + m), including validationO(1).
concat and +O(n + m)O(n + m) result.
splitO(n + m + k)O(n + k) owned fields and List; O(m) search buffer.
joinO(n + p + k + r)O(r) result and O(k) borrowed view metadata; no part payload copies.
replaceO(n + m + s + r)O(r) result and O(m) search buffer.
lower, upper, casefoldO(n + r), with fixed Unicode tablesO(r) result; O(1) auxiliary with fixed Unicode tables.
repeatO(n + r)O(r) result; O(1) auxiliary.
String(Int64/Float64/Bool)O(1) for bounded scalar representationO(1) bounded result.
String(String)O(n)O(n) owned result.

Search uses a prefix table algorithm rather than repeated naive substring comparisons. Unicode table versions and bounded numeric widths are fixed for these bounds. A String occupies O(n) persistent storage. Current interpreter value evaluation can additionally copy owned String arguments; that copy is linear in their bytes. These native operation bounds do not imply a process memory quota or constant time indexing.

Error behavior and limits

Invalid UTF-8, invalid indices or intervals, empty split/count/replacement patterns, non String join elements, negative repetition, nonfinite Float64 conversion, and exceeded text limits produce R3201, located at the call or operator. Static receiver and argument type mismatches are rejected before execution.

Native text operations admit at most 16 MiB per input String or produced String and at most 262,144 split fields. The shared object heap's List object, value slot, and stored String budgets also apply to split results and join inputs. Bounds are checked before producing an oversized result. These per operation bounds are separate from overall process memory. A runtime failure follows the normal program or persistent session error contract.

Run the complete String example with:

sh
aner check examples/string_basic.aner
aner examples/string_basic.aner

See Type inspection for the distinction between value type and element storage datatype.

Aner documentation · Reference and guides