Skip to content

The language

Strings

A str is immutable UTF-8 text. len(s), s[i] and slices count bytes, not characters; for c in s and s.chars() walk characters. Methods never change a string; they return a new one.

fn main():
let s = "hello, brine"
let name = "brine"
print(s.upper(), len(s), s[7:], s.find("brine"))
print(f"{name} has {len(name)} letters, pi is {3.14159:.2f}")
Output
HELLO, BRINE 12 brine Some(7)
brine has 5 letters, pi is 3.14

Strings use double quotes (single quotes work too). These escapes are the only ones:

Escape Meaning
\n, \t, \r, \0 newline, tab, carriage return, NUL
\\, \", \' backslash, quotes
\{, \} literal braces
\u{XXXX} a Unicode code point in hex: "\u{e9}" is "é"

Anything else, such as \x41, is error E0101.

fn main():
let s = "héllo"
print(len(s), len(s.chars()))
print(s[0], s[1:3], s[3:])
print(s.chars())
print(s.bytes())
for c in s:
print(c, end=" ")
print()
Output
6 5
h é llo
["h", "é", "l", "l", "o"]
[104, 195, 169, 108, 108, 111]
h é l l o
  • len(s) is the number of bytes. é takes two bytes in UTF-8, so "héllo" has length 6.
  • s[i] is a one-byte str. For ASCII text that’s the character at i. Inside a multi-byte character it’s a single byte, which isn’t valid text on its own, so for non-ASCII text use s.chars() or for c in s.
  • s[a:b] is the bytes from a up to b. Indexes outside the string and a > b panic. There are no negative indexes: write s[len(s) - 1].
  • s.chars() is a [str] of characters; s.bytes() is an [int] of byte values.

Why bytes? Byte indexing is constant time and is what parsers and file formats need. Python’s character indexing costs either memory (fixed-width storage) or time.

Strings can’t be changed in place:

fn main():
var s = "abc"
s[0] = "x"
error[E0404]: cannot assign to a character of a string
--> main.br:3:5
|
3 | s[0] = "x"
| ^^^^ strings can't be changed in place
|
= help: build a new string, e.g. `s = s[:i] + "x" + s[i + 1:]`
= run `brine explain E0404` for more about this error
error: could not compile due to 1 error

+ joins two strings. += on a var appends, growing the string in place when nothing else holds it, so building a string in a loop is fast:

fn main():
var out = ""
for i in 0..5:
out += str(i)
out += " "
let line = "[" + out.rstrip() + "]"
print(line, "-".join(["a", "b", "c"]), "ab".repeat(3))
Output
[0 1 2 3 4] a-b-c ababab

+ needs two strings. "n = " + 5 is error E0305: convert with str(5) or use an f-string. "ab" * 3 is an error too; write "ab".repeat(3).

Strings compare with ==, !=, <, <=, > and >= (byte order), and x in s tests for a substring.

fn main():
let s = "hello, brine"
print(s.find("brine"), s.find("zzz"), s.count("l"))
print(s.starts_with("he"), s.ends_with("!"), "ri" in s, s.contains("ri"))
print(s.split(", "), "a b c".split(), "a,,b".split(","))
print(s.replace("l", "L"), "-".join(["x", "y"]), "ab".repeat(2))
print("[" + " x ".strip() + "]", "[" + " x ".lstrip() + "]", "[" + " x ".rstrip() + "]")
print("straße".upper(), "ÉCOLE".lower())
print("one\ntwo\r\nthree\n".lines())
Output
Some(7) None 2
true false true true
["hello", "brine"] ["a", "b", "c"] ["a", "", "b"]
heLLo, brine x-y abab
[x] [x ] [ x]
STRASSE école
["one", "two", "three"]
Method Result What it does
s.find(sub) Option[int] byte index of the first sub, or None
s.contains(sub), sub in s bool substring test
s.count(sub) int non-overlapping occurrences
s.starts_with(p), s.ends_with(p) bool
s.split(sep) [str] split on every sep; keeps empty parts
s.split() [str] split on runs of whitespace; no empty parts
sep.join(xs) str join a [str] with sep between
s.replace(old, new) str replace every old
s.repeat(n) str n copies
s.strip(), s.lstrip(), s.rstrip() str remove whitespace from both ends, the start, the end
s.upper(), s.lower() str Unicode case conversion
s.lines() [str] split on \n or \r\n; no trailing empty line
s.chars() [str] each character
s.bytes() [int] each byte (0 to 255)
s.is_digit() bool non-empty and only 0-9
s.is_alpha(), s.is_alnum() bool Unicode letters (and digits)
s.is_space() bool Unicode whitespace
len(s) int length in bytes
  • find, split(sep), replace and count panic when the pattern is empty (split() separator is empty). find("") is Some(0).
  • strip() and its two variants take no arguments; they remove whitespace only.
  • Case conversion and is_alpha/is_alnum/is_space follow Unicode, as in Python: "straße".upper() is "STRASSE". is_digit accepts only 0-9, the digits int() reads, so "٣".is_digit() is false.
fn main():
print(ord("a"), ord("é"), chr(97), chr(0x1F600))
print("42".is_digit(), "-4".is_digit(), "".is_digit(), "é".is_alpha(), " \t".is_space())
Output
97 233 a 😀
true false false true true

ord(c) needs exactly one character and panics otherwise (ord() needs exactly one character, got a string of length 2). chr(n) panics for negative numbers and other values that aren’t Unicode scalar values.

An f before the quote makes an f-string: each {expr} is replaced by the value of the expression, printed the way print shows it. {{ and }} are literal braces.

fn main():
let name = "brine"
let xs = [1, 2]
let n = 42
print(f"{name.upper()} {n + 1} {xs} {n > 40} {{braces}}")
Output
BRINE 43 [1, 2] true {braces}

An expression inside an f-string can’t contain a string literal (f"{"a"}" is an error); put the string in a variable first.

Add :spec after the expression to format a number or pad a value. The spec is [<|>][0][width][.precision][kind]:

fn main():
let pi = 3.14159265
let n = 42
let name = "brine"
print(f"[{pi:.2f}] [{pi:.0f}] [{pi:e}] [{pi:.3e}] [{pi:.2}]")
print(f"[{n:5}] [{n:<5}] [{n:>5}] [{n:05}] [{-n:05}]")
print(f"[{n:x}] [{n:b}] [{255:08b}] [{n:.1f}]")
print(f"[{name:8}] [{name:<8}] [{pi:10.3f}] [{pi:<10.3f}] [{pi:08.2f}]")
Output
[3.14] [3] [3.141593e+00] [3.142e+00] [3.14]
[ 42] [42 ] [ 42] [00042] [-0042]
[2a] [101010] [11111111] [42.0]
[ brine] [brine ] [ 3.142] [3.142 ] [00003.14]
Spec Meaning Example
.Nf fixed point, N decimals {pi:.2f} → 3.14
.N the same as .Nf {pi:.2} → 3.14
e, .Ne scientific (6 decimals by default) {pi:.3e} → 3.142e+00
x hexadecimal, integers only {255:x} → ff
b binary, integers only {5:b} → 101
N minimum width N, right-aligned {n:5} → 42
<N left-align in N columns {n:<5} → 42
>N right-align (the default) {n:>5} → 42
0N pad with zeros after the sign {n:05} → 00042
  • Every value is right-aligned by default, strings too (Python left-aligns strings). Use < for left-aligned columns of text.
  • f, e and .N need a number; an integer is printed as a float ({42:.1f} is 42.0). x and b need an integer.
  • There is no centering (^), no fill character other than 0, and no , thousands separator. An unsupported spec is error E0318:
fn main():
let n = 7
print(f"{n:^5}")
error[E0318]: invalid format spec `^5`
--> main.br:3:14
|
3 | print(f"{n:^5}")
| ^ in this f-string field
|
= help: supported: `.2f` (fixed), `.3e` (scientific), `8` (width), `<8` (left-align), `08` (zero-pad), `x` (hex), `b` (binary)
= run `brine explain E0318` for more about this error
error: could not compile due to 1 error

str(x) converts any printable value. int(s) and float(s) parse text, and so do the sized types (i32("7"), u8("200"), f32("1.5")):

fn main():
print(str(42) + "!", str(2.5), str(true), str([1, 2]), str(Some(3)))
print(int("42") + 1, int("-7"), int(" 42"), float("2.5"), float("1e3"))
print(str(1.0), str(0.1 + 0.2), str(1e20), str(1e-7))
Output
42! 2.5 true [1, 2] Some(3)
43 -7 42 2.5 1000.0
1.0 0.30000000000000004 1e+20 1e-07

Bad input panics. Text from outside the program (files, arguments) should be checked first, for example with is_digit():

fn main():
let text = "4x2"
print(int(text))
Output
panic: invalid integer: "4x2"
--> main.br:3:11
Call Panics when
int(s) s isn’t a decimal integer ("3.5", "0x1f" and "1_000" all panic) or doesn’t fit in an int
u8(s), i32(s), … the same, or the number is out of range for the type
float(s) s isn’t a number; "inf" and "nan" are accepted
ord(c) c isn’t exactly one character
chr(n) n isn’t a valid code point

Surrounding whitespace is ignored when parsing (int(" 42\n") is 42).

Floats print in the shortest form that reads back as the same value, always with a decimal point or exponent: 1.0, 0.30000000000000004, 1e+20.

  • Strings are immutable UTF-8. Methods return new strings.
  • len, s[i] and slices count bytes; for c in s and s.chars() count characters.
  • No negative indexes. Out-of-range indexes and slices panic.
  • + joins strings only; convert other values with str() or an f-string.
  • int() and float() panic on bad input.

Related: arrays, built-in functions, values and types, error handling.