Standard Library — Functions and Methods
Contents
Built-in Functions
print
fn print(value: str) -> ()
Writes a string to standard output without a trailing newline.
print("hello ")
print("world")
# output: hello world
Because the parameter type is str, non-string values must be converted before passing them to print. String interpolation handles this automatically:
print("count: {42}") # int satisfies ToString, so interpolation works
print is available in all source files without import.
println
fn println(value: str) -> ()
Writes a string to standard output followed by a newline (\n).
println("hello world")
println("count: {42}")
println is the standard way to produce line-oriented output. Like print, the parameter type is str — use string interpolation to format non-string values.
println is available in all source files without import.
readln
fn readln() -> Result[str, IoError]
Reads a single line of text from standard input. On success, returns Ok(str) containing the line without the trailing newline character (\n or \r\n). If the input stream contains a line terminator, it is consumed but stripped from the result.
print("What is your name? ")
match readln()
Ok(name) then println("Hello, {name}!")
Err(e) then println("Error reading input")
end
Error conditions: Returns Err(IoError) when:
- Standard input has reached end-of-file (EOF) — returns
Err(IoError.Eof) - An I/O error occurs (e.g., stdin is closed) — returns
Err(IoError.Other(msg))wheremsgdescribes the error - Reading from a non-interactive source fails — returns
Err(IoError.Other(msg))
Partial line at EOF: If EOF is reached after reading one or more characters but before encountering a line terminator, readln returns Ok(str) containing the characters read (treating EOF as an implicit line terminator). If EOF is reached immediately (before any characters are read), returns Err(IoError.Eof).
See the IoError enum definition in stdlib/types.md.
readln blocks until a complete line is available or an error occurs. It is intended for interactive and scripting use. For file I/O, see the io standard library module (stdlib/io.md). Programs that require non-blocking or byte-level I/O are not yet available (see roadmap.md).
readln is available in all source files without import.
panic
fn panic(message: str) -> never
Terminates execution immediately with a runtime error. The message is included in the error output. The exact output format and termination mechanism are implementation-defined.
Because panic returns never, it can appear in any expression context — the type checker treats it as satisfying any type:
fn unwrap[T](opt: Option[T]) -> T
match opt
Some(value) then return value
None then panic("unwrap called on None")
end
end
panic is available in all source files without import.
toList
fn toList[T](iter: Iterable[T]) -> List[T]
mut result: List[T] = []
for item in iter
result.push(item)
end
return result
end
Materializes any iterable into a List[T]. This is a free function rather than a method on Iterable[out T] because List[T] is invariant — placing it as a return type on a covariant interface would violate variance checking rules.
doubledEvens = items.map(fn(x) x * 2)
.filter(fn(x) x > 5) |> toList # List[int]
toList is available in all source files without import.
toMap
fn toMap[K, V](iter: Iterable[Tuple[K, V]]) -> Map[K, V]
where K: Eq[K] & Hash
mut result: Map[K, V] = {}
for (k, v) in iter
result[k] = v
end
return result
end
Materializes an iterable of key-value tuples into a Map[K, V]. If duplicate keys appear, the last value wins. The where clause requires K to satisfy Eq[K] and Hash, matching the constraints of Map[K, V] itself.
pairs = [(1, "one"), (2, "two"), (3, "three")]
m = pairs.iter() |> toMap # Map[int, str]
Like toList, this is a free function rather than a method on Iterable for variance correctness.
toMap is available in all source files without import.
toSet
fn toSet[T](iter: Iterable[T]) -> Set[T]
where T: Eq[T] & Hash
mut result: Set[T] = Set.from([])
for item in iter
result.add(item)
end
return result
end
Materializes any iterable into a Set[T], discarding duplicates. The where clause requires T to satisfy Eq[T] and Hash, matching the constraints of Set[T] itself.
unique = [1, 2, 3, 2, 1].iter().filter(fn(x) x > 1) |> toSet # Set[int]
Like toList, this is a free function rather than a method on Iterable for variance correctness.
toSet is available in all source files without import.
Interface Conformance Summary
The following table summarizes which built-in types satisfy which prelude interfaces. "Conditional" means the conformance depends on type parameters satisfying the same interface.
| Type | PartialEq |
Eq |
Hash |
Comparable |
ToString |
Iterable |
|---|---|---|---|---|---|---|
bool |
✅ | ✅ | ✅ | ❌ | ✅ | ❌ |
byte |
✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
int |
✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
uint |
✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
float |
✅² | ❌³ | ✅⁴ | ✅⁵ | ✅ | ❌ |
str |
✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
Option[T] |
conditional | conditional | conditional | ❌ | conditional | ❌ |
Result[T, E] |
conditional | conditional | conditional | ❌ | conditional | ❌ |
GeneratorResult[Y, R] |
conditional | conditional | conditional | ❌ | conditional | ❌ |
List[T] |
conditional | conditional | ❌⁶ | ❌ | conditional | ✅ |
Map[K, V] |
conditional | conditional | ❌⁶ | ❌ | conditional | ✅ (pairs) |
Set[T] |
✅ | ✅ | ❌⁶ | ❌ | conditional | ✅ |
Buffer |
✅ | ✅ | ❌⁶ | ❌ | ✅ | ✅ |
Tuple[...] |
conditional | conditional | conditional | ❌ | conditional | ❌ |
Generator[Y, R, N] |
❌ | ❌ | ❌ | ❌ | ❌ | ✅¹ |
¹ Generator[T, (), never] satisfies Iterable[T]. Generators with non-() R or non-never N do not satisfy Iterable.
² float implements PartialEq[float] with IEEE 754 semantics: NaN != NaN, -0.0 == 0.0. The == and != operators desugar through PartialEq.eq() like any other type. See lang/semantics/values.md §2.11.2.
³ float does not implement Eq[float] because IEEE 754 NaN violates the reflexivity guarantee (NaN != NaN). Generic code constrained by T: Eq[T] cannot use float.
⁴ float: Hash guarantees 0.0 and -0.0 produce the same hash. All NaN bit patterns produce the same hash. Note that float cannot be used as a Map key or Set element because both require Eq, which float does not satisfy (see ³). The Hash conformance exists for use in compound types (e.g., a struct containing a float field that implements Eq by excluding NaN values).
⁵ float: Comparable — comparison operators (<, >, <=, >=) on float bypass Comparable desugaring and use IEEE 754 intrinsics (all NaN comparisons return false). The compare method provides a total ordering where NaN sorts after all other values. See lang/semantics/values.md §2.11.2.
⁶ List[T], Map[K, V], Set[T], and Buffer intentionally omit Hash. All four are mutable containers — hashing a mutable object is dangerous because mutations after insertion would invalidate the hash, breaking map lookup invariants. Tuple[...] implements Hash conditionally because tuples are immutable, making their hash values stable.
Conditional conformance rules:
Option[T]: PartialEqwhenT: PartialEq[T]Option[T]: EqwhenT: Eq[T]Option[T]: HashwhenT: HashResult[T, E]: PartialEqwhenT: PartialEq[T]andE: PartialEq[E]Result[T, E]: EqwhenT: Eq[T]andE: Eq[E]Result[T, E]: HashwhenT: HashandE: HashGeneratorResult[Y, R]: PartialEqwhenY: PartialEq[Y]andR: PartialEq[R]GeneratorResult[Y, R]: EqwhenY: Eq[Y]andR: Eq[R]GeneratorResult[Y, R]: HashwhenY: HashandR: HashOption[T]: ToStringwhenT: ToStringResult[T, E]: ToStringwhenT: ToStringandE: ToStringGeneratorResult[Y, R]: ToStringwhenY: ToStringandR: ToStringList[T]: PartialEqwhenT: PartialEq[T]List[T]: EqwhenT: Eq[T]List[T]: ToStringwhenT: ToStringMap[K, V]: PartialEqwhenK: PartialEq[K]andV: PartialEq[V]Map[K, V]: EqwhenK: Eq[K]andV: Eq[V]Map[K, V]: ToStringwhenK: ToStringandV: ToStringSet[T]: PartialEq— always (T: Eq[T] & Hashis a type constraint ofSet)Set[T]: Eq— alwaysSet[T]: ToStringwhenT: ToStringTuple[...]: PartialEqwhen all element types satisfyPartialEqTuple[...]: Eqwhen all element types satisfyEqTuple[...]: Hashwhen all element types satisfyHashTuple[...]: ToStringwhen all element types satisfyToString
User-defined types: Structs must explicitly declare implements InterfaceName and provide methods whose visibility matches the interface (pub for pub interfaces, pkg for pkg interfaces, private for private interfaces) — see lang/structs/core.md §4.5.
Built-in type conformance: All built-in types (byte, int, uint, float, str, bool, Option[T], Result[T, E], List[T], Map[K, V], Set[T], Buffer, Tuple[...], Generator[Y, R, N]) have compiler-provided interface conformance. These types do not have visible implements declarations — the conformance rules are built into the language and documented in this section. Enums have automatic PartialEq, Eq, and ToString conformance without requiring implements declarations. For enums with associated data, ToString conformance is conditional on all associated data types satisfying ToString; simple enums (no associated data) satisfy ToString unconditionally.
Primitive Type Methods
The built-in primitive types have methods that are always available. These are not defined via interfaces — they are intrinsic to the types themselves.
Methods without a self parameter are static methods, called on the type name directly (e.g., int.parse("42"), float.INFINITY). This is consistent with how struct static methods work (see §4.1, §4.3). Methods with a self parameter are instance methods, called on a value.
Methods can be called on literal values directly:
x = 42.toFloat() # 42.0
y = 3.7.round() # Some(4)
z = 255b.toInt() # 255
bool Methods
bool has no intrinsic methods.
byte Methods
Conversion:
| Signature | Description |
|---|---|
fn toInt(self) -> int |
Convert to int. Always succeeds (lossless widening). |
fn toUint(self) -> uint |
Convert to uint. Always succeeds (lossless widening). |
fn toFloat(self) -> float |
Convert to float. Always succeeds (lossless widening). |
Arithmetic helpers:
| Signature | Description |
|---|---|
fn pow(self, exp: byte) -> byte |
Raise self to the power exp. Panics on overflow. 0.pow(0) returns 1. |
fn min(self, other: byte) -> byte |
Return the smaller of self and other. |
fn max(self, other: byte) -> byte |
Return the larger of self and other. |
fn clamp(self, low: byte, high: byte) -> byte |
Clamp self to the range [low, high]. Panics if low > high. |
Wrapping arithmetic (modular wrap-around, never panics):
| Signature | Description |
|---|---|
fn wrappingAdd(self, other: byte) -> byte |
Addition with modular wrapping (mod 256). |
fn wrappingSub(self, other: byte) -> byte |
Subtraction with modular wrapping (mod 256). |
fn wrappingMul(self, other: byte) -> byte |
Multiplication with modular wrapping (mod 256). |
Saturating arithmetic (clamps to 0 / 255, never panics):
| Signature | Description |
|---|---|
fn saturatingAdd(self, other: byte) -> byte |
Addition clamped to [0, 255]. |
fn saturatingSub(self, other: byte) -> byte |
Subtraction clamped to [0, 255]. |
fn saturatingMul(self, other: byte) -> byte |
Multiplication clamped to [0, 255]. |
Parsing:
| Signature | Description |
|---|---|
fn parse(s: str) -> Result[byte, str] |
Parse an unsigned 8-bit integer from a string. Returns Err with a description on failure. |
byte.parse accepts an optional leading + sign followed by one or more digits. Decimal is the default. The prefixes 0x (hex), 0o (octal), and 0b (binary) are recognized, matching literal syntax. Underscore separators are NOT accepted. A leading - sign is not accepted. The b suffix used in byte literals (e.g., 42b) is not accepted in parsed strings.
Returns Err if the string is empty, contains invalid characters, or the value exceeds 255.
byte.parse("42") # Ok(42b)
byte.parse("255") # Ok(255b)
byte.parse("0xFF") # Ok(255b)
byte.parse("0b11111111") # Ok(255b)
byte.parse("256") # Err("overflow")
byte.parse("-1") # Err("invalid character: -")
byte.parse("42b") # Err("invalid character: b")
byte.parse("") # Err("empty string")
Constants (accessed on the type, not on a value):
| Expression | Value |
|---|---|
byte.MIN |
0 |
byte.MAX |
255 |
x: byte = 200b
x + 100b # panic: integer overflow
x.wrappingAdd(100b) # 44b (wraps mod 256)
x.saturatingAdd(100b) # 255b (clamped)
y: byte = 0b
y - 1b # panic: integer overflow
y.wrappingSub(1b) # 255b (wraps)
y.saturatingSub(1b) # 0b (clamped)
# Conversions
42b.toInt() # 42
42b.toUint() # 42u
42b.toFloat() # 42.0
int Methods
Conversion:
| Signature | Description |
|---|---|
fn toFloat(self) -> float |
Convert to float. Exact for integers up to 2^53; larger values may lose precision. |
fn toUint(self) -> uint |
Convert to uint. Panics if self is negative. |
fn toByte(self) -> byte |
Convert to byte. Panics if self is outside 0–255. |
fn wrappingToUint(self) -> uint |
Reinterpret the bit pattern as uint. Never panics. |
fn wrappingToByte(self) -> byte |
Truncate to the low 8 bits. Never panics. |
Arithmetic helpers:
| Signature | Description |
|---|---|
fn pow(self, exp: int) -> int |
Raise self to the power exp. Panics if exp is negative. Panics on overflow. 0.pow(0) returns 1. |
fn abs(self) -> int |
Absolute value. Panics if self is int.MIN (result would overflow). |
fn min(self, other: int) -> int |
Return the smaller of self and other. |
fn max(self, other: int) -> int |
Return the larger of self and other. |
fn clamp(self, low: int, high: int) -> int |
Clamp self to the range [low, high]. Panics if low > high. |
Wrapping arithmetic (two's complement wrap-around, never panics):
| Signature | Description |
|---|---|
fn wrappingAdd(self, other: int) -> int |
Addition with two's complement wrapping. |
fn wrappingSub(self, other: int) -> int |
Subtraction with two's complement wrapping. |
fn wrappingMul(self, other: int) -> int |
Multiplication with two's complement wrapping. |
Saturating arithmetic (clamps to int.MIN / int.MAX, never panics):
| Signature | Description |
|---|---|
fn saturatingAdd(self, other: int) -> int |
Addition clamped to [int.MIN, int.MAX]. |
fn saturatingSub(self, other: int) -> int |
Subtraction clamped to [int.MIN, int.MAX]. |
fn saturatingMul(self, other: int) -> int |
Multiplication clamped to [int.MIN, int.MAX]. |
Parsing:
| Signature | Description |
|---|---|
fn parse(s: str) -> Result[int, str] |
Parse a signed integer from a string. Returns Err with a description on failure. |
int.parse accepts an optional sign (+ or -) followed by one or more digits. Decimal is the default. The prefixes 0x (hex), 0o (octal), and 0b (binary) are recognized, matching literal syntax. Hex prefixes (0x, 0X) and hex digits (A-F, a-f) are case-insensitive. For example, 0xFF, 0xff, 0XFF, and 0Xff are all valid and equivalent. Underscore separators are NOT accepted - they are only valid in source code literals, not in parsed strings. Leading and trailing whitespace is not accepted.
Returns Err if the string is empty, contains invalid characters, or the value overflows the signed 64-bit range.
int.parse("42") # Ok(42)
int.parse("-100") # Ok(-100)
int.parse("+7") # Ok(7)
int.parse("0xFF") # Ok(255)
int.parse("0b1010") # Ok(10)
int.parse("0o77") # Ok(63)
int.parse("1_000") # Err("invalid character: _")
int.parse("") # Err("empty string")
int.parse("hello") # Err("invalid character: h")
int.parse("99999999999999999999") # Err("overflow")
Constants (accessed on the type, not on a value):
| Expression | Value |
|---|---|
int.MIN |
−9,223,372,036,854,775,808 (−2^63) |
int.MAX |
9,223,372,036,854,775,807 (2^63 − 1) |
n = -5
n.abs() # 5
n.toFloat() # -5.0
n.toUint() # panic: negative int cannot convert to uint
n.toByte() # panic: value outside 0–255
42.toFloat() # 42.0
42.toByte() # 42b
10.min(3) # 3
10.max(20) # 20
15.clamp(0, 10) # 10
# Wrapping conversions
(-1).wrappingToByte() # 255b (low 8 bits of two's complement)
256.wrappingToByte() # 0b
# Wrapping and saturating
x = int.MAX
x + 1 # panic: integer overflow
x.wrappingAdd(1) # -9_223_372_036_854_775_808 (wraps)
x.saturatingAdd(1) # 9_223_372_036_854_775_807 (clamped)
uint Methods
Conversion:
| Signature | Description |
|---|---|
fn toFloat(self) -> float |
Convert to float. Exact for values up to 2^53; larger values may lose precision. |
fn toInt(self) -> int |
Convert to int. Panics if self exceeds int.MAX. |
fn toByte(self) -> byte |
Convert to byte. Panics if self exceeds 255. |
fn wrappingToInt(self) -> int |
Reinterpret the bit pattern as int. Never panics. |
fn wrappingToByte(self) -> byte |
Truncate to the low 8 bits. Never panics. |
Arithmetic helpers:
| Signature | Description |
|---|---|
fn pow(self, exp: uint) -> uint |
Raise self to the power exp. Panics on overflow. 0u.pow(0u) returns 1u. |
fn min(self, other: uint) -> uint |
Return the smaller of self and other. |
fn max(self, other: uint) -> uint |
Return the larger of self and other. |
fn clamp(self, low: uint, high: uint) -> uint |
Clamp self to the range [low, high]. Panics if low > high. |
Wrapping arithmetic (modular wrap-around, never panics):
| Signature | Description |
|---|---|
fn wrappingAdd(self, other: uint) -> uint |
Addition with modular wrapping. |
fn wrappingSub(self, other: uint) -> uint |
Subtraction with modular wrapping. |
fn wrappingMul(self, other: uint) -> uint |
Multiplication with modular wrapping. |
Saturating arithmetic (clamps to 0 / uint.MAX, never panics):
| Signature | Description |
|---|---|
fn saturatingAdd(self, other: uint) -> uint |
Addition clamped to [0, uint.MAX]. |
fn saturatingSub(self, other: uint) -> uint |
Subtraction clamped to [0, uint.MAX]. |
fn saturatingMul(self, other: uint) -> uint |
Multiplication clamped to [0, uint.MAX]. |
Parsing:
| Signature | Description |
|---|---|
fn parse(s: str) -> Result[uint, str] |
Parse an unsigned decimal integer from a string. Returns Err with a description on failure. |
uint.parse accepts an optional leading + sign followed by one or more digits. Decimal is the default. The prefixes 0x (hex), 0o (octal), and 0b (binary) are recognized, matching literal syntax. Underscore separators are NOT accepted - they are only valid in source code literals, not in parsed strings. A leading - sign is not accepted. Leading and trailing whitespace is not accepted. The u suffix used in uint literals (e.g., 42u) is not accepted in parsed strings.
Returns Err if the string is empty, contains invalid characters, or the value overflows the unsigned 64-bit range.
uint.parse("42") # Ok(42u)
uint.parse("+100") # Ok(100u)
uint.parse("0xFF") # Ok(255u)
uint.parse("0b1010") # Ok(10u)
uint.parse("0o77") # Ok(63u)
uint.parse("1_000") # Err("invalid character: _")
uint.parse("-1") # Err("invalid character: -")
uint.parse("42u") # Err("invalid character: u")
uint.parse("") # Err("empty string")
Constants (accessed on the type, not on a value):
| Expression | Value |
|---|---|
uint.MIN |
0 |
uint.MAX |
18,446,744,073,709,551,615 (2^64 − 1) |
x: uint = 42u
x.toFloat() # 42.0
x.toInt() # 42
x.toByte() # 42b
300u.toByte() # panic: value exceeds 255
300u.wrappingToByte() # 44b (low 8 bits)
y: uint = 0u
y - 1u # panic: integer overflow
y.wrappingSub(1u) # 18_446_744_073_709_551_615u (wraps)
y.saturatingSub(1u) # 0u (clamped)
float Methods
Conversion:
| Signature | Description |
|---|---|
fn round(self) -> Option[int] |
Round to the nearest integer. Halfway values (.5) round up (toward +∞). Returns None if NaN, infinite, or out of int range. |
fn floor(self) -> Option[int] |
Round toward −∞. Returns None if NaN, infinite, or out of int range. |
fn ceiling(self) -> Option[int] |
Round toward +∞. Returns None if NaN, infinite, or out of int range. |
fn roundUint(self) -> Option[uint] |
Round to the nearest unsigned integer. Returns None if NaN, infinite, negative, or out of uint range. |
fn floorUint(self) -> Option[uint] |
Round toward −∞, returning uint. Returns None if NaN, infinite, negative, or out of uint range. |
fn ceilingUint(self) -> Option[uint] |
Round toward +∞, returning uint. Returns None if NaN, infinite, negative, or out of uint range. |
Arithmetic helpers:
| Signature | Description |
|---|---|
fn pow(self, exp: float) -> float |
Raise self to the power exp. Follows IEEE 754 rules; never panics. Negative exponents are allowed: 2.0.pow(-3.0) produces 0.125. 0.0.pow(0.0) returns 1.0. |
fn abs(self) -> float |
Absolute value. (-0.0).abs() returns 0.0. NaN.abs() returns NaN. |
fn min(self, other: float) -> float |
Return the smaller of self and other. If either is NaN, returns NaN. |
fn max(self, other: float) -> float |
Return the larger of self and other. If either is NaN, returns NaN. |
fn clamp(self, low: float, high: float) -> float |
Clamp to [low, high]. Returns NaN if self is NaN. Panics if low > high or if low or high is NaN. |
fn sqrt(self) -> float |
Square root. Returns NaN for negative values. |
Special-value testing:
| Signature | Description |
|---|---|
fn isNan(self) -> bool |
true if self is NaN. |
fn isInfinite(self) -> bool |
true if self is positive or negative infinity. |
fn isFinite(self) -> bool |
true if self is neither NaN nor infinite. |
Parsing:
| Signature | Description |
|---|---|
fn parse(s: str) -> Result[float, str] |
Parse a floating-point number from a string. Returns Err with a description on failure. |
float.parse accepts standard decimal floating-point notation: an optional sign (+ or -), integer digits, an optional decimal point with fractional digits, and an optional exponent (e or E followed by an optional sign and digits). The special strings "Infinity", "-Infinity", and "NaN" are also accepted.
Special value strings are case-sensitive. Only exact matches "Infinity", "-Infinity", and "NaN" are accepted. Variants like "inf", "infinity", "nan", or "+Infinity" return Err.
Leading and trailing whitespace is not accepted.
float.parse("3.14") # Ok(3.14)
float.parse("-0.5") # Ok(-0.5)
float.parse("1e10") # Ok(10000000000.0)
float.parse("2.5E-3") # Ok(0.0025)
float.parse("42") # Ok(42.0)
float.parse("Infinity") # Ok(float.INFINITY)
float.parse("-Infinity") # Ok(float.NEG_INFINITY)
float.parse("NaN") # Ok(float.NAN)
float.parse("") # Err("empty string")
float.parse("hello") # Err("invalid character: h")
Values that overflow float range produce infinity (not an error), consistent with IEEE 754 overflow semantics. Values that underflow (too small to represent as normalized floats) produce subnormal numbers or zero, following IEEE 754 gradual underflow semantics.
Constants (accessed on the type, not on a value):
| Expression | Description |
|---|---|
float.INFINITY |
Positive infinity (+∞). |
float.NEG_INFINITY |
Negative infinity (−∞). |
float.NAN |
A quiet NaN value. |
float.MAX |
Largest finite positive value (~1.8 × 10^308). |
float.MIN |
Most negative finite value (~−1.8 × 10^308). Equal to -float.MAX. |
float.MIN_POSITIVE |
Smallest positive non-zero value (~5.0 × 10^−324). |
float.EPSILON |
Difference between 1.0 and the next representable float (~2.2 × 10^−16). |
3.7.round() # Some(4)
3.2.round() # Some(3)
2.5.round() # Some(3) (0.5 rounds up)
(-2.5).round() # Some(-2)
3.7.floor() # Some(3)
(-3.2).floor() # Some(-4)
3.2.ceiling() # Some(4)
(-3.7).ceiling() # Some(-3)
# Special values return None
float.NAN.round() # None
float.INFINITY.floor() # None
(-1.0 * float.INFINITY).ceilingUint() # None — negative infinity
# Special values
x = 0.0 / 0.0
x.isNan() # true
(1.0 / 0.0).isInfinite() # true
3.14.isFinite() # true
# Constants — no literal syntax for special values
inf = float.INFINITY
nan = float.NAN
println("{inf}") # "Infinity"
println("{nan}") # "NaN"
str Methods
| Signature | Description |
|---|---|
fn byteLength(self) -> uint |
Number of bytes (UTF-8 code units) in the string. |
fn charLength(self) -> uint |
Number of Unicode grapheme clusters in the string. Multi-codepoint sequences (emojis with variation selectors, combining characters) count as one. |
fn isEmpty(self) -> bool |
true if byte length is 0. |
fn contains(self, substr: str) -> bool |
true if substr appears anywhere in the string at a grapheme cluster boundary. Case-sensitive. |
fn startsWith(self, prefix: str) -> bool |
true if the string starts with prefix. |
fn endsWith(self, suffix: str) -> bool |
true if the string ends with suffix. |
fn indexOf(self, substr: str) -> Option[uint] |
Grapheme cluster index of the first occurrence at a grapheme cluster boundary, or None. Consistent with slice and charLength indexing. indexOf("") returns Some(0u) for any string, including the empty string (the empty string is trivially found at position 0). |
fn slice(self, start: uint, end: uint) -> str? |
Return a substring by grapheme cluster range. See below for semantics. |
fn trim(self) -> str |
Remove leading and trailing whitespace. |
fn toUpper(self) -> str |
Convert ASCII characters to uppercase. |
fn toLower(self) -> str |
Convert ASCII characters to lowercase. |
fn split(self, separator: str) -> List[str] |
Split into a list of substrings by separator. |
fn replace(self, old: str, new: str) -> str |
Replace all occurrences of old with new. |
fn repeat(self, count: uint) -> str |
Return the string repeated count times. |
fn charAt(self, index: uint) -> Option[str] |
Return the grapheme cluster at index, or None if out of bounds. See below. |
fn bytes(self) -> Iterator[byte] |
Return an iterator over the raw UTF-8 bytes of the string. |
fn toBuffer(self) -> Buffer |
Return a Buffer containing the UTF-8 encoded bytes. Always succeeds. |
fn fromBytes(b: Buffer) -> Result[str, str] |
Static method. Validate b as UTF-8 and return a str. Returns Err with a description if invalid. |
No bracket access. Strings do not support bracket access (str[i]). Use charAt(i) or slice(i, i + 1) for single-character access by grapheme cluster index. Bracket access is intentionally omitted because grapheme cluster indexing is O(n), and the explicit method call makes this cost visible.
slice semantics: Indices are grapheme cluster positions (zero-based), consistent with charLength and split(""). start is inclusive, end is exclusive.
- If
start > self.charLength(), returnsNone(range is entirely out of bounds). The casestart == self.charLength()is valid and returnsSome("")(an empty slice at the end of the string). - If
endexceedsself.charLength(), it is clamped toself.charLength()(partial in-bounds range returns the in-bounds portion). - If
end <= startafter clamping, returnsSome(""). - Slicing an empty string with
start = 0returnsSome("")(0 is a valid start position on an empty string).
"hello".slice(0, 3) # Some("hel")
"hello".slice(1, 4) # Some("ell")
"hello".slice(3, 10) # Some("lo") — end clamped to 5
"hello".slice(0, 0) # Some("")
"hello".slice(6, 8) # None — start > charLength
"❤️🎉".slice(0, 1) # Some("❤️") — grapheme cluster semantics
"".slice(0, 5) # Some("")
"".slice(1, 5) # None
Case conversion: toUpper and toLower affect only ASCII characters (code points 0x00-0x7F). Non-ASCII characters are passed through unchanged. For Unicode case conversion (e.g., Turkish İ/i, German ß), use a third-party library.
String splitting: split(separator: str) divides the string into substrings by searching for the separator. Edge case behavior:
- If
separatoris the empty string"", the string is split into individual Unicode grapheme clusters. Each grapheme cluster becomes a separate list element. - If
separatordoes not appear in the string, returns a single-element list containing the original string unchanged.
"hello".byteLength() # 5
"hello".charLength() # 5
"❤️".byteLength() # 6 (multi-byte emoji)
"❤️".charLength() # 1 (one grapheme cluster)
"hello".contains("ell") # true
"hello".startsWith("he") # true
"hello world".split(" ") # ["hello", "world"]
"hello".split("") # ["h", "e", "l", "l", "o"]
"❤️🎉".split("") # ["❤️", "🎉"]
"hello".split("x") # ["hello"]
" hi ".trim() # "hi"
"abc".repeat(3u) # "abcabcabc"
"hello".replace("l", "r") # "herro"
"ab".replace("", "-") # "-a-b-"
"".replace("", "-") # "-"
String replacement: replace(old: str, new: str) replaces all non-overlapping occurrences of old with new. If old is the empty string "", new is inserted at each grapheme cluster boundary: before the first character, between characters, and after the last character. For a string with N grapheme clusters, this produces N+1 insertions. For the empty string (0 grapheme clusters), new is inserted once.
Character access: charAt(index) returns the grapheme cluster at the given zero-based index as a single-character string, or None if the index is out of bounds. Like slice, indexing is by grapheme cluster position and is O(n). charAt(i) is equivalent to slice(i, i + 1) but avoids the range ceremony.
"hello".charAt(0u) # Some("h")
"hello".charAt(4u) # Some("o")
"hello".charAt(5u) # None
"❤️🎉".charAt(0u) # Some("❤️")
"❤️🎉".charAt(1u) # Some("🎉")
Byte-level access: bytes() returns an Iterator[byte] over the raw UTF-8 encoded bytes of the string. Each step is O(1). This is the efficient way to process string contents byte-by-byte — for example, lexing ASCII-heavy source code. The iterator yields bytes in order from the first to the last.
"Hi".bytes() |> toList # [72b, 105b]
"❤️".bytes() |> toList # [0xE2b, 0x9Db, 0xA4b, 0xEFb, 0xB8b, 0x8Fb] — 6 UTF-8 bytes
Buffer conversion: toBuffer() returns a Buffer containing the raw UTF-8 bytes of the string. Always succeeds because strings are always valid UTF-8. str.fromBytes(b) is the inverse — it validates the buffer contents as UTF-8 and returns the string, or Err if the bytes are not valid UTF-8.
buf = "Hello".toBuffer() # Buffer containing [72, 101, 108, 108, 111]
buf.length() # 5u
buf.toStr() # Ok("Hello") — via Buffer.toStr()
str.fromBytes(buf) # Ok("Hello") — via str.fromBytes()
str.fromBytes(Buffer.from([0xFFb])) # Err("invalid UTF-8 at byte 0")
str.fromBytes(b) and buf.toStr() perform the same validation and produce the same result. Both are provided for ergonomic use — fromBytes reads naturally when starting from a Buffer, while toStr reads naturally when chaining methods on a buffer.
Substring matching and grapheme clusters: All substring operations (contains, indexOf, startsWith, endsWith, split, replace) match only at grapheme cluster boundaries. A match is recognized only when the substring starts at the beginning of a grapheme cluster and ends at the end of a grapheme cluster. If a byte-level match would start or end inside a multi-codepoint grapheme cluster, it is not considered a match. For example, searching for the combining acute accent "\u0301" inside "e\u0301" (the grapheme cluster "é") returns no match, because the accent does not start at a cluster boundary.