Str
Builtin.Str :: # (opaque)
Returns guidance about string length instead of a number.
A string can have different lengths depending on what you count: UTF-8
bytes, Unicode code points, or grapheme clusters (an approximation of
user-perceived characters). For example, é can be represented as one
code point or as e followed by a combining accent. These representations
use different numbers of bytes and code points, but each is one grapheme
cluster. A grapheme cluster's display width also depends on how it is rendered.
Use Str.count_utf8_bytes when you need the byte count, or Str.is_empty
to check whether a string is empty. For other measurements, choose a
Unicode library that supports the unit you need. For text known to be
ASCII, each code point occupies one byte; this does not hold for arbitrary
Unicode text.
Concatenates two strings together.
expect "ab".concat("cd") == "abcd"
expect "hello".concat("") == "hello"
expect "".concat("") == ""Determines whether or not the first Str contains the second.
expect "foobarbaz".contains("bar")
expect !"apple".contains("orange")
expect "anything".contains("")Return the Str with all whitespace removed from both the beginning
as well as the end.
expect " Hello \n\n".trim() == "Hello"Return the Str with all whitespace removed from the beginning.
expect " Hello \n\n".trim_start() == "Hello \n\n"Return the Str with all whitespace removed from the end.
expect " Hello \n\n".trim_end() == " Hello"Returns True if all the ASCII characters in both strings are the same,
ignoring differences in capitalization.
Non-ASCII characters must all be exactly the same,
including capitalization. For example:
expect "café".caseless_ascii_equals("CAFé")
expect !"café".caseless_ascii_equals("CAFÉ")The first call returns True because all the ASCII characters are the same
when ignoring differences in capitalization, and the only non-ASCII character
(é) is the same in both strings. The second call returns False because
é and É are not ASCII characters, and they are different.
This function is useful for things like command-line flags and environment variable names where you know in advance that you're dealing with a string containing only ASCII characters. It has better performance than lowercasing operations which take Unicode into account.
That said, strings received from user input can always contain
non-ASCII Unicode characters, and lowercasing Unicode works
differently in different languages. For example, the string "I" lowercases to "i"
in English and to "ı" (a dotless i)
in Turkish. These rules can also change in each Unicode release,
so we have separate unicode package
for Unicode capitalization that can be upgraded independently from the language's builtins.
To convert a string's ASCII characters to uppercase or lowercase, you can use Str.with_ascii_uppercased
or Str.with_ascii_lowercased.
Returns a version of the string with all ASCII characters lowercased. Non-ASCII characters are left unmodified. For example:
expect "CALFÉ".with_ascii_lowercased() == "calfÉ"This function is useful for things like command-line flags and environment variable names where you know in advance that you're dealing with a string containing only ASCII characters. It has better performance than lowercasing operations which take Unicode into account.
That said, strings received from user input can always contain
non-ASCII Unicode characters, and lowercasing Unicode works
differently in different languages. For example, the string "I" lowercases to "i"
in English and to "ı" (a dotless i)
in Turkish. These rules can also change in each Unicode release,
so we have separate unicode package
for Unicode capitalization that can be upgraded independently from the language's builtins.
To do a case-insensitive comparison of the ASCII characters in a string,
you can use Str.caseless_ascii_equals.
Returns a version of the string with all ASCII characters uppercased. Non-ASCII characters are left unmodified. For example:
expect "café".with_ascii_uppercased() == "CAFé"This function is useful for things like command-line flags and environment variable names where you know in advance that you're dealing with a string containing only ASCII characters. It has better performance than uppercasing operations which take Unicode into account.
That said, strings received from user input can always contain
non-ASCII Unicode characters, and uppercasing Unicode
works differently in different languages.
For example, the string "i" uppercases to "I" in English and to "İ"
(a dotted I) in Turkish.
These rules can also change in each Unicode release,
so we have a separate unicode package for Unicode capitalization
that can be upgraded independently from the language's builtins.
To do a case-insensitive comparison of the ASCII characters in a string,
you can use Str.caseless_ascii_equals.
Check if the given Str starts with a value.
expect "ABC".starts_with("A") == Bool.True
expect "ABC".starts_with("X") == Bool.FalseCheck if the given Str ends with a value.
expect "ABC".ends_with("C") == Bool.True
expect "ABC".ends_with("X") == Bool.FalseRepeats a string the given number of times.
expect "z".repeat(3) == "zzz"
expect "na".repeat(8) == "nananananananana"Returns "" when given "" for the string or 0 for the count.
expect "".repeat(10) == ""
expect "anything".repeat(0) == ""Adds a prefix to the given Str.
expect "Awesome".with_prefix("Roc") == "RocAwesome"Drops the given prefix from the start of a Str, comparing ASCII
letters without regard to capitalization. Non-ASCII bytes must match
exactly.
The returned Str is a slice of the original string. If the prefix is
not found, returns Err(NotFound).
expect "Cache-Control: max-age=0".drop_prefix_caseless_ascii("cache-control") == Ok(": max-age=0")
expect "X-Api-Key".drop_prefix_caseless_ascii("x_api_key") == Err(NotFound)Splits the string on the first occurrence of a delimiter, returning the parts before and after it.
The returned strings are slices of the original string.
expect "foo: bar".split_first(":") == Ok({ before: "foo", after: " bar" })
expect "foo".split_first(":") == Err(NotFound)Splits the string on the last occurrence of a delimiter, returning the parts before and after it.
The returned strings are slices of the original string.
expect "a.b.c".split_last(".") == Ok({ before: "a.b", after: "c" })
expect "foo".split_last(":") == Err(NotFound)Replaces every occurrence of delimiter in a string with replacement.
Returns the string unchanged when delimiter is empty or not found.
expect "a,b,c".replace_each(",", " and ") == "a and b and c"
expect "abc".replace_each("x", "y") == "abc"Replaces the first occurrence of delimiter in a string with replacement.
Returns the string unchanged when delimiter is empty or not found.
expect "a,b,a".replace_first(",", " and ") == "a and b,a"
expect "abc".replace_first("x", "y") == "abc"Replaces the last occurrence of delimiter in a string with replacement.
Returns the string unchanged when delimiter is empty or not found.
expect "a,b,a".replace_last(",", " and ") == "a,b and a"
expect "abc".replace_last("x", "y") == "abc"Gives the number of bytes in a Str value.
expect "Hello World".count_utf8_bytes() == 11Iterate over this string's UTF-8 code units in byte order. The iterator's known length is the string's byte length. Iteration does not allocate.
Drop a byte count from the start of a string. Counts at or beyond the
byte length return Ok(""); an in-range cut inside a UTF-8 code point
returns Err(BadUtf8). Valid slices do not allocate.
Drop a byte count from the end of a string. Counts at or beyond the byte
length return Ok(""); an in-range cut inside a UTF-8 code point returns
Err(BadUtf8). Valid slices do not allocate.
Returns a string of the specified capacity without any content.
This is like calling Str.reserve on an empty string. It's intended for
building up a string incrementally, for example by calling Str.concat on it:
greeting = "Hello and welcome to Roc"
subject = "Awesome Programmer"
# Evaluates to "Hello and welcome to Roc, Awesome Programmer!"
# Note that string interpolation with "${greeting}, ${subject}!" would be preferred here,
# this is just a simple example to demonstrate `Str.with_capacity`.
hello_world =
Str.with_capacity(45)
.concat(greeting)
.concat(", ")
.concat(subject)
.concat("!")When the final size is known up front, Str.with_capacity guarantees that
subsequent calls to Str.concat will not need to reallocate. Whether this
is faster than starting from "" depends on the system allocator: many
allocators can extend an existing allocation in place, making the difference
small or negligible. The benefit is most pronounced when reallocation would
otherwise force a full copy of the string.
If you don't know the exact capacity, passing a value larger than necessary still avoids reallocation, at the cost of using more memory than is needed.
For more details, see Str.reserve.
Increase a string's capacity by at least the given number of additional bytes.
When you plan to append more bytes onto a string, Str.reserve can help
avoid intermediate reallocations during a chain of Str.concat calls.
Consider the following example, which does not use Str.reserve:
greeting = "Hello and welcome to Roc"
subject = "Awesome Programmer"
# Evaluates to "Hello and welcome to Roc, Awesome Programmer!"
hello_world =
greeting
.concat(", ")
.concat(subject)
.concat("!")In this example:
- We start with
greeting, which has both a length and capacity of 24 bytes. .concat(", ")sees there isn't enough capacity for 2 more bytes, so it allocates a new 26-byte buffer, copies the existing 24 bytes into it, and writes", "at the end. The old allocation is deallocated..concat(subject)repeats the process: allocate a 44-byte buffer, copy the 26 existing bytes, write"Awesome Programmer"..concat("!")repeats once more: allocate 45 bytes, copy 44, write"!".
With Str.reserve, the chain only needs one allocation up front:
greeting = "Hello and welcome to Roc"
subject = "Awesome Programmer"
hello_world =
greeting
.reserve(21)
.concat(", ")
.concat(subject)
.concat("!")Here, .reserve(21) ensures there is room for an additional 21 bytes
(", " + "Awesome Programmer" + "!"), allocating a 45-byte buffer
and copying greeting into it. The subsequent Str.concat calls all fit
in that capacity and do not need to reallocate.
Whether this is actually faster depends on the system allocator: many
allocators can extend an existing allocation in place, in which case the
per-concat reallocation is cheap and Str.reserve makes little observable
difference. The benefit is most pronounced when reallocation would
otherwise force a full copy of the string.
Str.reserve is not free—when more capacity is needed, it always
performs a heap allocation. Only use it when you actually expect to make
use of the extra capacity.
When you don't know exactly how many bytes you'll need, choosing a value somewhat higher than necessary is usually safe; a value that's too low may force later reallocation, while a value much higher than necessary just wastes memory.
If you plan to use Str.reserve on an empty string, use Str.with_capacity
instead.
Shrink the memory footprint of a str such that its capacity and length are equal. Note: This will also convert seamless slices to regular lists.
Returns a List of the string's U8 UTF-8 code units.
(To split the string into a List of smaller Str values instead of U8 values,
see Str.split_on.)
expect "Roc".to_utf8() == [82, 111, 99]
expect "鹏".to_utf8() == [233, 185, 143]
expect "சி".to_utf8() == [224, 174, 154, 224, 174, 191]
expect "🐦".to_utf8() == [240, 159, 144, 166]Converts a List of U8 UTF-8 code units to a string.
Any grouping of invalid byte sequences are replaced with a single unicode replacement character '�'.
An invalid byte sequence is defined as
- a 2-byte-sequence starting byte, followed by less than 1 continuation byte
- a 3-byte-sequence starting byte, followed by less than 2 continuation bytes
- a 4-byte-sequence starting byte, followed by less than 3 continuation bytes
- an invalid codepoint from the surrogate pair block
- an invalid codepoint greater than 0x110000 encoded as a 4-byte sequence
- any valid codepoint encoded as an incorrect sequence, for instance a codepoint that should be a 2-byte sequence encoded as a 3- or 4-byte sequence
expect Str.from_utf8_lossy([82, 111, 99, 240, 159, 144, 166]) == "Roc🐦"
expect Str.from_utf8_lossy([82, 255, 99]) == "R�c"
expect Str.from_utf8_lossy([82, 0xED, 0xA0, 0xBD, 99]) == "R�c"Converts a List of U8 UTF-8 code units to a string.
Returns Err if the given bytes are invalid UTF-8, and returns Ok("") when given [].
expect Str.from_utf8([82, 111, 99]) == Ok("Roc")
expect Str.from_utf8([233, 185, 143]) == Ok("鹏")
expect Str.from_utf8([224, 174, 154, 224, 174, 191]) == Ok("சி")
expect Str.from_utf8([240, 159, 144, 166]) == Ok("🐦")
expect Str.from_utf8([]) == Ok("")
expect Str.from_utf8([255]).is_err()Decodes little-endian UTF-16 bytes. Preserves U+FEFF, including at the start.
Returns the first malformed sequence's zero-based byte offset and problem.
A partial final code unit reports UnexpectedEndOfSequence; empty input succeeds.
expect Str.from_utf16_le([65, 0]) == Ok("A")
expect Str.from_utf16_le([]) == Ok("")Decodes little-endian UTF-16 bytes, preserving U+FEFF as text. Replaces each unpaired surrogate and any partial final code unit with U+FFFD. Empty input returns an empty string.
expect Str.from_utf16_le_lossy([65, 0]) == "A"
expect Str.from_utf16_le_lossy([65]) == "�"Decodes big-endian UTF-16 bytes. Preserves U+FEFF, including at the start.
Returns the first malformed sequence's zero-based byte offset and problem.
A partial final code unit reports UnexpectedEndOfSequence; empty input succeeds.
expect Str.from_utf16_be([0, 65]) == Ok("A")
expect Str.from_utf16_be([]) == Ok("")Decodes big-endian UTF-16 bytes, preserving U+FEFF as text. Replaces each unpaired surrogate and any partial final code unit with U+FFFD. Empty input returns an empty string.
expect Str.from_utf16_be_lossy([0, 65]) == "A"
expect Str.from_utf16_be_lossy([65]) == "�"from_utf16_bom : List(U8) -> Try(Str, [BadUtf16({ problem : Utf16Problem, index : U64 }), MissingByteOrderMark])Decodes UTF-16 bytes with a required leading byte order mark.
Consumes exactly one marker; subsequent U+FEFF characters remain text.
Missing, incomplete, or unrecognized markers return MissingByteOrderMark.
Malformed payload indices are byte offsets in the original input, including the marker.
expect Str.from_utf16_bom([255, 254]) == Ok("")
expect Str.from_utf16_bom([]) == Err(MissingByteOrderMark)Requires and consumes a leading UTF-16 byte order mark, then decodes lossily. A missing marker is an error; malformed payload is replaced with U+FFFD.
expect Str.from_utf16_bom_lossy([255, 254]) == Ok("")
expect Str.from_utf16_bom_lossy([]) == Err(MissingByteOrderMark)Decodes little-endian UTF-32 bytes. Preserves U+FEFF, including at the start.
Returns the first malformed sequence's zero-based byte offset and problem.
A partial final code unit reports UnexpectedEndOfSequence; empty input succeeds.
expect Str.from_utf32_le([65, 0, 0, 0]) == Ok("A")
expect Str.from_utf32_le([]) == Ok("")Decodes little-endian UTF-32 bytes, preserving U+FEFF as text. Replaces each invalid scalar and any partial final code unit with U+FFFD. Empty input returns an empty string.
expect Str.from_utf32_le_lossy([65, 0, 0, 0]) == "A"
expect Str.from_utf32_le_lossy([65]) == "�"Decodes big-endian UTF-32 bytes. Preserves U+FEFF, including at the start.
Returns the first malformed sequence's zero-based byte offset and problem.
A partial final code unit reports UnexpectedEndOfSequence; empty input succeeds.
expect Str.from_utf32_be([0, 0, 0, 65]) == Ok("A")
expect Str.from_utf32_be([]) == Ok("")Decodes big-endian UTF-32 bytes, preserving U+FEFF as text. Replaces each invalid scalar and any partial final code unit with U+FFFD. Empty input returns an empty string.
expect Str.from_utf32_be_lossy([0, 0, 0, 65]) == "A"
expect Str.from_utf32_be_lossy([65]) == "�"from_utf32_bom : List(U8) -> Try(Str, [BadUtf32({ problem : Utf32Problem, index : U64 }), MissingByteOrderMark])Decodes UTF-32 bytes with a required leading byte order mark.
Consumes exactly one marker; subsequent U+FEFF characters remain text.
Missing, incomplete, or unrecognized markers return MissingByteOrderMark.
Malformed payload indices are byte offsets in the original input, including the marker.
expect Str.from_utf32_bom([255, 254, 0, 0]) == Ok("")
expect Str.from_utf32_bom([]) == Err(MissingByteOrderMark)Requires and consumes a leading UTF-32 byte order mark, then decodes lossily. A missing marker is an error; malformed payload is replaced with U+FFFD.
expect Str.from_utf32_bom_lossy([255, 254, 0, 0]) == Ok("")
expect Str.from_utf32_bom_lossy([]) == Err(MissingByteOrderMark)Converts an interpolated string literal to a Str.
The compiler calls this at compile time with the literal's segments: the text before the first interpolation, then the text after each one. It returns the function the compiler calls at runtime with the interpolated values, in order, to assemble the string.
expect Str.from_interpolation(["a", "c"]).map_ok(|assemble| assemble(["b"])) == Ok("abc")Split a string around a separator.
Passing "" for the separator is not useful;
it returns the original string wrapped in a List.
expect "1,2,3".split_on(",") == ["1","2","3"]
expect "1,2,3".split_on("") == ["1,2,3"]Combines a List of strings into a single string, with a separator
string in between each.
expect Str.join_with(["one", "two", "three"], ", ") == "one, two, three"
expect Str.join_with(["1", "2", "3", "4"], ".") == "1.2.3.4"Returns True if the two strings are exactly the same.
Feed a string into a Hasher.
Returns a human-readable representation of a value, useful for debugging.
Encode a string using a format that provides encode_str
Decode a string using a format that provides decode_str
Utf8Problem
:= [
InvalidStartByte,
UnexpectedEndOfSequence,
ExpectedContinuation,
OverlongEncoding,
CodepointTooLarge,
EncodesSurrogateHalf,
]Utf16Problem
:= [UnpairedHighSurrogate, UnpairedLowSurrogate, UnexpectedEndOfSequence]
Utf32Problem
:= [CodePointTooLarge, SurrogateCodePoint, UnexpectedEndOfSequence]