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
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.
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("🧪")) // 5get(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-1when absent. An empty needle returns0.contains,starts_with, andends_withaccept 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 nonemptyoldand 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.
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")) // bXnarepeat(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.
print(" Straße ".trim().upper()) // STRASSE
print("Straße".casefold() == "STRASSE".casefold()) // trueCase 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.
| Method | Result | Contract |
|---|---|---|
byte_len() | Int64 | Count UTF-8 bytes after validation. |
casefold() | String | Apply full default Unicode case folding. |
compare(other: String) | Int64 | Return ordinal comparison as −1, 0, or 1. |
concat(other: String) | String | Append the other text. |
contains(needle: String) | Bool | Test whether literal text occurs. |
count(needle: String) | Int64 | Count nonoverlapping occurrences of a nonempty needle. |
ends_with(suffix: String) | Bool | Test a literal suffix. |
find(needle: String) | Int64 | Return the first scalar index, or −1. |
get(index: Int64) | String | Return exactly one scalar value at a valid index. |
is_empty() | Bool | Test whether validated text is empty. |
join(parts: List) | String | Join only String elements, using this receiver as separator. |
len() | Int64 | Count Unicode scalar values. |
lower() | String | Apply full default lowercase mapping. |
repeat(count: Int64) | String | Repeat the text a nonnegative number of times. |
replace(old: String, new: String) | String | Replace all nonoverlapping occurrences of nonempty old text. |
slice(start: Int64, end: Int64) | String | Copy the scalar interval [start, end). |
split(separator: String) | List | Split on a nonempty literal separator, preserving empty fields. |
starts_with(prefix: String) | Bool | Test a literal prefix. |
trim() | String | Remove White_Space at both ends. |
trim_end() | String | Remove White_Space at the end. |
trim_start() | String | Remove White_Space at the start. |
upper() | String | Apply 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.
| Operation | Time | Additional space |
|---|---|---|
len, byte_len, is_empty | O(n), including UTF-8 validation | O(1). |
get | O(n), including validation and scalar lookup | O(1): one scalar occupies at most four bytes. |
slice, trim, trim_start, trim_end | O(n + r) | O(r) result; O(1) auxiliary. |
find, contains, count | O(n + m) | O(m) search prefix buffer; O(1) scalar result. |
starts_with, ends_with, compare | O(n + m), including validation | O(1). |
concat and + | O(n + m) | O(n + m) result. |
split | O(n + m + k) | O(n + k) owned fields and List; O(m) search buffer. |
join | O(n + p + k + r) | O(r) result and O(k) borrowed view metadata; no part payload copies. |
replace | O(n + m + s + r) | O(r) result and O(m) search buffer. |
lower, upper, casefold | O(n + r), with fixed Unicode tables | O(r) result; O(1) auxiliary with fixed Unicode tables. |
repeat | O(n + r) | O(r) result; O(1) auxiliary. |
String(Int64/Float64/Bool) | O(1) for bounded scalar representation | O(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:
aner check examples/string_basic.aner
aner examples/string_basic.anerSee Type inspection for the distinction between value type and element storage datatype.