Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Strings and Collections

Pluto’s built-in collection types: strings, byte buffers, arrays, maps, and sets. Plus std.collections for functional operations on arrays.

Strings

String literals use double quotes. Concatenation with +. Interpolation with {} inside an f-prefixed string; a plain string never interpolates.

let s = "hello " + "world"
let name = "alice"
print(f"hello {name}")          // hello alice
print(f"{1 + 2}")               // 3
print(f"use {{braces}}")        // use {braces}  (escape with doubling)
print(f"hello {name}")           // hello {name}  (no f prefix: literal braces)

Escape sequences: \n, \r, \t, \\, \"

Indexing returns a one-byte string (read-only; s[0] = "x" is a compile error). Strings are UTF-8 and indexed by byte, so a non-ASCII character such as é spans several indices:

let s = "hello"
print(s[0])                     // h

Iteration yields one-byte strings, so it splits multi-byte (non-ASCII) characters:

for c in "abc" {
    print(c)                    // a, b, c
}

String Methods

MethodSignatureReturns
.len()() intLength in bytes
.contains(sub)(string) boolSubstring check
.starts_with(pre)(string) boolPrefix check
.ends_with(suf)(string) boolSuffix check
.index_of(sub)(string) intFirst occurrence, or -1
.last_index_of(sub)(string) intLast occurrence, or -1
.count(sub)(string) intNon-overlapping occurrences
.trim()() stringStrip leading/trailing whitespace
.trim_start()() stringStrip leading whitespace
.trim_end()() stringStrip trailing whitespace
.is_empty()() boolLength == 0
.is_whitespace()() boolEmpty or all whitespace
.repeat(n)(int) stringConcatenate n copies (0 or less gives "")
.to_upper()() stringUppercase copy (ASCII letters only; other characters unchanged)
.to_lower()() stringLowercase copy (ASCII letters only; other characters unchanged)
.replace(old, new)(string, string) stringReplace all occurrences
.split(delim)(string) [string]Split into array; "" splits by char
.substring(offset, len)(int, int) stringSubstring by offset and length
.char_at(idx)(int) stringOne-byte string at byte index
.byte_at(idx)(int) intByte value at index (0-255)
.to_int()() int?Parse as integer, none on failure
.to_float()() float?Parse as float, none on failure
.to_bytes()() bytesConvert to byte buffer

to_int() and to_float() return nullable types. Use ? to propagate:

fn parse(s: string) int? {
    let v = s.to_int()?
    return v * 2
}

Method chaining works naturally: " Hello ".trim().to_lower().contains("hello") returns true.

Bytes

The byte type is an unsigned 8-bit integer (0-255). No byte literal – convert from int with .to_byte(). It returns byte? in general (none outside 0..255), but a literal or a value proven in range narrows to a plain byte; .low_byte() keeps the low 8 bits of any int by name.

let b: byte = (0xFF).to_byte()
print(b.to_int())               // 255
print((300).low_byte().to_int()) // 44

Byte ordering is unsigned: (0xFF).to_byte() > (0x7F).to_byte() is true.

Byte Buffers

Create with bytes_new(). Type is bytes. Supports .push(), .len(), indexing (read/write), and iteration.

let mut buf = bytes_new()
buf.push((72).to_byte())
buf.push((105).to_byte())
print(buf.len())                // 2
print(buf[0].to_int())          // 72
buf[0] = (90).to_byte()         // index write

for b in buf {                  // iteration yields byte values
    print(b.to_int())
}

String conversion – roundtrips cleanly:

let buf = "Hello".to_bytes()
let s = buf.to_string()         // "Hello"

Arrays

Literal syntax with type [T]. All elements must have the same type.

let nums = [1, 2, 3]           // [int]
let names = ["a", "b"]         // [string]

Indexing (read and write) and iteration:

let a = [10, 20, 30]
print(a[0])                     // 10
a[1] = 99                       // index write

for x in a {
    print(x)
}

Array Methods

MethodSignatureReturns
.len()() intElement count
.push(val)(T)Append to end
.pop()() TRemove and return last
.first()() TFirst element
.last()() TLast element
.is_empty()() boolLength == 0 check
.clear()()Remove all elements
.contains(val)(T) boolElement presence
.index_of(val)(T) intFirst index, or -1
.remove_at(idx)(int) TRemove at index, shift left
.insert_at(idx, val)(int, T)Insert at index, shift right
.slice(start, end)(int, int) [T]Sub-array [start, end)
.reverse()()Reverse in place

Arrays work as function parameters (fn f(a: [int])) and return values (fn f() [int]).

Maps

Hash maps with typed keys and values. Keys must be hashable: int, float, bool, string, byte, or enum.

let m = Map<string, int> { "a": 1, "b": 2 }
let empty = Map<string, int> {}

print(m["a"])                   // 1
m["c"] = 3                      // insert via index
m["a"] = 99                     // overwrite

Map Methods

MethodSignatureReturns
.len()() intNumber of entries
.insert(key, val)(K, V)Insert or overwrite
.contains(key)(K) boolKey presence
.remove(key)(K)Remove entry
.keys()() [K]Array of all keys
.values()() [V]Array of all values

Iteration via .keys() or .values():

for k in m.keys() {
    print(f"{k}: {m[k]}")
}

Sets

Unordered collections with unique elements. Same hashable constraint as map keys.

let s = Set<int> { 1, 2, 3 }
let empty = Set<string> {}

Set Methods

MethodSignatureReturns
.len()() intNumber of elements
.insert(val)(T)Add element (no-op if present)
.contains(val)(T) boolMembership check
.remove(val)(T)Remove element
.to_array()() [T]Convert to array (for iteration)

Duplicate inserts are silently ignored. Iterate via .to_array():

for x in s.to_array() {
    print(x)
}

std.collections

Functional operations on arrays. Import with import std.collections.

import std.collections

fn main() {
    let nums = [1, 2, 3, 4, 5]
    let doubled = collections.map(nums, (x: int) => x * 2)
    let evens = collections.filter(nums, (x: int) => x % 2 == 0)
    let total = collections.fold(nums, 0, (acc: int, x: int) => acc + x)
}

Function Reference

FunctionSignatureDescription
map(arr, f)([T], fn(T) U) [U]Transform each element
filter(arr, f)([T], fn(T) bool) [T]Keep matching elements
fold(arr, init, f)([T], U, fn(U, T) U) ULeft fold with accumulator
reduce(arr, f)([T], fn(T, T) T) TFold using first element as seed
any(arr, f)([T], fn(T) bool) boolAny element matches
all(arr, f)([T], fn(T) bool) boolAll elements match
count(arr, f)([T], fn(T) bool) intCount matching elements
flat_map(arr, f)([T], fn(T) [U]) [U]Map then flatten
for_each(arr, f)([T], fn(T))Side-effecting iteration
reverse(arr)([T]) [T]New reversed array
take(arr, n)([T], int) [T]First n elements
drop(arr, n)([T], int) [T]Skip first n elements
zip(a, b)([A], [B]) [Pair<A, B>]Pair elements by index
enumerate(arr)([T]) [Pair<int, T>]Pair each element with its index
flatten(arr)([[T]]) [T]Flatten one level of nesting
sum(arr)([int]) intSum of integers
sum_float(arr)([float]) floatSum of floats

zip and enumerate return Pair<A, B> (from std.collections) with fields first and second:

import std.collections

fn main() {
    let pairs = collections.enumerate(["alice", "bob"])
    for p in pairs {
        print(f"{p.first}: {p.second}")  // 0: alice, 1: bob
    }
}