Module

std::string

String Module

Owned, heap-backed UTF-8 byte string type and related utilities.

Overview

String is a value-type record whose data buffer lives on the heap. It implements Drop so the buffer is freed automatically when the owning binding goes out of scope, and Clone for explicit deep copies. Its storage is an owned UTF-8 byte buffer with a byte-counted len field.

str is the primitive immutable string-slice type (const char* in C). In v0.4 it remains a borrowed NUL-terminated UTF-8 byte view. String literals have type str. Use String::create(s) to create an owned copy from a str.

Interior NUL bytes are preserved in owned storage. toStr() is a zero-copy interop view. C-string consumers may stop at the first interior NUL.

Memory Layout

  String
  ┌──────────────┐
  │ data ────────┼──────► ┌───┬───┬───┬───┬───┬───┬───┬───┐
  │ len: 5       │        │ h │ e │ l │ l │ o │   │   │   │
  │ cap: 8       │        └───┴───┴───┴───┴───┴───┴───┴───┘
  └──────────────┘        ◄── used bytes ──►◄─ reserved ──►

Fields mirror the C runtime’s IgnisString layout exactly:

  • data — pointer to the backing byte buffer (or null before init)
  • len — number of bytes currently stored
  • cap — total bytes allocated

UTF-8 Scanning and Bytes

String stays byte-backed. APIs that need parser or diagnostic offsets keep those byte positions internal to the caller while decoding char values at explicit byte boundaries.

charAt and pushChar work with decoded char values, while byteAt, pushByte, forEachByte, findByte, trimWhere, and split keep raw byte behavior explicit.

Higher-Order Iteration Methods

String provides callback-based operations for char/byte iteration, searching, trimming, and splitting:

Method Callback signature Returns Description
forEach (char) -> void void Iterate scalar char values
forEachByte (u8) -> void void Iterate over every byte
map (char) -> char String Transform scalar char values
mapBytes (u8) -> u8 String Transform bytes directly
findByte (u8) -> boolean Option<u64> Index of first matching byte
findLastByte (u8) -> boolean Option<u64> Index of last matching byte
trimWhere (u8) -> boolean String Strip leading/trailing bytes
split (u8) -> boolean Vector<String> Split into segments

All callbacks are @noescape: the closure must not outlive the method call.

Byte Conversion

Method Returns Description
toBytes Vector<u8> Copy bytes into a byte vector
toChars Vector<char> Copy bytes into a char vector

Numeric Conversions

String::create is overloaded for all numeric primitives (i8 through f64), plus str. Extension methods provide .toString() on every primitive type.

Example

import String from "std::string";
import Io from "std::io";

function main(): i32 {
  let greeting: String = String::create("hello");
  let world: str = " world";
  let msg: String = greeting.concat(world);

  Io::println(msg);

  // Byte-level: trim whitespace and split on commas
  let csv: String = String::create("  a,b,c  ");
  let trimmed: String = csv.trimWhere((b: u8): boolean -> { return b == 32; });
  let parts: Vector<String> = trimmed.split((b: u8): boolean -> { return b == 44; });
  // parts: ["a", "b", "c"]

  return 0;
}
namespace __string

Functions

function ignis_string_byte_at(s: &String, idx: u64): u8

Returns the raw byte at idx; callers wrap bounds checks.

function ignis_string_char_at(s: &String, idx: u64, outEnd: &mut u64): char

Returns the scalar at byte idx and writes the exclusive end byte offset.

function ignis_string_clear(s: &mut String): void

Clears s without freeing its capacity.

function ignis_string_compare(a: &String, b: &String): i32

Compares two strings byte-wise using runtime ordering semantics.

function ignis_string_contains(haystack: &String, needle: &String): boolean

Returns whether haystack contains needle.

function ignis_string_cstr(s: &String): str

Returns the NUL-terminated borrowed view of s.

function ignis_string_drop(s: &mut String): void

Releases the runtime allocation held by s.

function ignis_string_index_of(haystack: &String, needle: &String): i64

Returns the byte index of needle in haystack, or -1.

function ignis_string_init_clone(out: &mut String, s: &String): void

Initializes out as a deep clone of s.

function ignis_string_init_concat(out: &mut String, a: &String, b: &String): void

Initializes out as the concatenation of a and b.

function ignis_string_init_from_cstr(out: &mut String, s: str): void

Initializes out by copying a NUL-terminated string view.

function ignis_string_init_from_f32(out: &mut String, value: f32): void

Initializes out with the decimal text of an f32.

function ignis_string_init_from_f64(out: &mut String, value: f64): void

Initializes out with the decimal text of an f64.

function ignis_string_init_from_i16(out: &mut String, value: i16): void

Initializes out with the decimal text of an i16.

function ignis_string_init_from_i32(out: &mut String, value: i32): void

Initializes out with the decimal text of an i32.

function ignis_string_init_from_i64(out: &mut String, value: i64): void

Initializes out with the decimal text of an i64.

function ignis_string_init_from_i8(out: &mut String, value: i8): void

Initializes out with the decimal text of an i8.

function ignis_string_init_from_len(out: &mut String, s: *mut u8, len: u64): void

Initializes out by copying len bytes from s, preserving interior NULs.

function ignis_string_init_from_u16(out: &mut String, value: u16): void

Initializes out with the decimal text of a u16.

function ignis_string_init_from_u32(out: &mut String, value: u32): void

Initializes out with the decimal text of a u32.

function ignis_string_init_from_u64(out: &mut String, value: u64): void

Initializes out with the decimal text of a u64.

function ignis_string_init_from_u8(out: &mut String, value: u8): void

Initializes out with the decimal text of a u8.

function ignis_string_init_new(out: &mut String): void

Initializes out as an empty runtime string.

function ignis_string_init_substring(out: &mut String, s: &String, start: i64, len: i64): void

Initializes out from a byte-range substring of s.

function ignis_string_init_to_lower(out: &mut String, s: &String): void

Initializes out as an ASCII lowercase copy of s.

function ignis_string_init_to_upper(out: &mut String, s: &String): void

Initializes out as an ASCII uppercase copy of s.

function ignis_string_init_with_capacity(out: &mut String, cap: u64): void

Initializes out as an empty runtime string with at least cap bytes.

function ignis_string_len(s: &String): u64

Returns the byte length of s.

function ignis_string_push_byte(s: &mut String, c: u8): void

Appends one raw byte to s.

function ignis_string_push_char(s: &mut String, c: char): void

Appends one Unicode-scalar char to s as UTF-8.

function ignis_string_push_cstr(s: &mut String, cstr: str): void

Appends a NUL-terminated string view to s.

function ignis_string_push_str(s: &mut String, other: &String): void

Appends the owned bytes from other to s.

function ignis_string_reserve(s: &mut String, additional: u64): void

Reserves room for at least additional more bytes.

namespace Utf8

UTF-8 byte-backed decoding helpers for String.

Functions

function byteAt(source: &String, index: u64): Option<u8>

Returns the byte at index from a string, if present.

function continuation(source: &String, index: u64): Option<u32>

Returns a UTF-8 continuation payload at index, if the byte is valid.

function decodeAt(source: &String, offset: u64, outEndByte: &mut u64): Option<char>

Decodes one UTF-8 scalar from source at byte offset.

Invalid byte sequences return U+FFFD covering the first invalid byte so the caller can make progress while preserving diagnostic offsets.

function invalidUtf8(startByte: u64, outEndByte: &mut u64): char

Returns a replacement character and advances by one byte.

Records

record String

Owned, heap-backed UTF-8 byte string.

The data buffer is heap-allocated and freed when the String is dropped. The record stores an owned UTF-8 byte buffer with byte-counted length and a trailing NUL byte for interop. Character APIs decode UTF-8 scalars while byte APIs remain explicit.

Implements Drop for automatic cleanup and Clone for deep copies.

Fields

  • data - Raw pointer to the heap byte buffer (null when uninitialized).
  • len - Number of bytes currently stored.
  • cap - Total capacity of the buffer in bytes.

Example

import String from "std::string";

let s: String = String::create("ignis");
let len: u64 = s.length();  // 5
let c: Option<char> = s.charAt(0);  // Option::SOME('i')
Members
data: *mut u8

Raw pointer to the heap byte buffer (null when uninitialized).

len: u64

Number of bytes currently stored.

cap: u64

Total capacity of the buffer in bytes.

static create(value: u8): String

Creates an owned String from a u8.

static create(value: u8): String

Creates an owned String from a u8.

static create(value: u8): String

Creates an owned String from a u8.

static create(value: u8): String

Creates an owned String from a u8.

static create(value: u8): String

Creates an owned String from a u8.

static create(value: u8): String

Creates an owned String from a u8.

static create(value: u8): String

Creates an owned String from a u8.

static create(value: u8): String

Creates an owned String from a u8.

static create(value: u8): String

Creates an owned String from a u8.

static create(value: u8): String

Creates an owned String from a u8.

static create(value: u8): String

Creates an owned String from a u8.

static create(value: u8): String

Creates an owned String from a u8.

static hexDigit(value: u8): char
static new(): String

Creates a new empty string with default capacity.

Example

import String from "std::string";

let mut s: String = String::new();
s.pushStr("hello");
static withCapacity(capacity: u64): String

Creates a new empty string with at least capacity bytes reserved.

Use this when you know roughly how many bytes the string will hold, to avoid repeated reallocations during push/pushStr/pushChar.

Arguments

  • capacity - Minimum number of bytes to reserve.

Example

import String from "std::string";

let mut s: String = String::withCapacity(256);
s.pushStr("pre-allocated buffer");
asBytes(&self): u8[]

Borrows the underlying bytes as a non-owning u8[] slice.

The returned slice preserves interior NUL bytes because String stores an explicit byte length. It is invalidated if the string is mutated in a way that reallocates its backing buffer.

byteAt(&self, index: u64): Option<u8>

Returns the raw byte at index, or Option::NONE when out of range.

charAt(&self, index: u64, outEndByte: &mut u64): Option<char>

Returns the decoded UTF-8 scalar at index and writes its exclusive byte end.

Use charAt() with byte offsets when you need Unicode scalar traversal without exposing extra public span or cursor records.

charAt(&self, index: u64, outEndByte: &mut u64): Option<char>

Returns the decoded UTF-8 scalar at index and writes its exclusive byte end.

Use charAt() with byte offsets when you need Unicode scalar traversal without exposing extra public span or cursor records.

charAt(&self, index: u64, outEndByte: &mut u64): Option<char>

Returns the decoded UTF-8 scalar at index and writes its exclusive byte end.

Use charAt() with byte offsets when you need Unicode scalar traversal without exposing extra public span or cursor records.

charAt(&self, index: u64, outEndByte: &mut u64): Option<char>

Returns the decoded UTF-8 scalar at index and writes its exclusive byte end.

Use charAt() with byte offsets when you need Unicode scalar traversal without exposing extra public span or cursor records.

clear(&mut self): void

Resets the string length to 0 without releasing the backing buffer.

Subsequent pushes reuse the existing capacity.

Example

import String from "std::string";

let mut s: String = String::create("hello");
s.clear();
// s.length() == 0, but capacity is still available
clone(&self): String

Creates a deep copy of this string.

Allocates a new buffer and copies all bytes. The returned String is completely independent from self.

Example

import String from "std::string";

let original: String = String::create("hello");
let copy: String = original.clone();
// original and copy are independent heap allocations
compare(&self, other: &String): i32

Lexicographically compares this string with other byte-by-byte.

Returns

  • Negative if self < other
  • Zero if self == other
  • Positive if self > other

Example

import String from "std::string";

let a: String = String::create("apple");
let b: String = String::create("banana");
let cmp: i32 = a.compare(&b);  // negative (a < b)
concat(&self, value: u8): String
concat(&self, value: u8): String
concat(&self, value: u8): String
concat(&self, value: u8): String
concat(&self, value: u8): String
concat(&self, value: u8): String
concat(&self, value: u8): String
concat(&self, value: u8): String
concat(&self, value: u8): String
concat(&self, value: u8): String
concat(&self, value: u8): String
concat(&self, value: u8): String
concat(&self, value: u8): String
concat(&self, value: u8): String
concat(&self, value: u8): String
concat(&self, value: u8): String
concat(&self, value: u8): String
concat(&self, value: u8): String
concat(&self, value: u8): String
concat(&self, value: u8): String
concat(&self, value: u8): String
concat(&self, value: u8): String
concat(&self, value: u8): String
concat(&self, value: u8): String
concat(&self, value: u8): String
concat(&self, value: u8): String
concat(&self, value: u8): String
concat(&self, value: u8): String
concatStr(&self, other: str): String

Alias overload for concat(str).

contains(&self, needle: &String): boolean

Returns true if needle appears anywhere in this string.

Arguments

  • needle - The substring to search for.

Example

import String from "std::string";

let s: String = String::create("hello world");
let needle: String = String::create("world");
let found: boolean = s.contains(&needle);  // true
drop(&mut self): void

Releases the backing byte buffer and resets all fields.

Called automatically when the String goes out of scope via @implements(Drop). Can also be called manually to release memory early.

equals(&self, other: str): boolean

Returns whether this owned string has the same NUL-terminated bytes as other.

equals(&self, other: str): boolean

Returns whether this owned string has the same NUL-terminated bytes as other.

findByte(&self, predicate: fn(u8) -> boolean): Option<u64>

Returns the byte index of the first byte for which predicate returns true, or Option::NONE if no byte matches.

Short-circuits: stops scanning at the first match.

Arguments

  • predicate - Callback that receives a byte and returns true to indicate a match.

Returns

Option::SOME(index) for the first matching byte, or Option::NONE.

Example

import String from "std::string";
import Option from "std::option";

let s: String = String::create("hello world");

// Find the first space (byte 32)
let idx: Option<u64> = s.findByte((b: u8): boolean -> { return b == 32; });
// idx == Option::SOME(5)

// Find a byte that doesn't exist
let missing: Option<u64> = s.findByte((b: u8): boolean -> { return b == 0; });
// missing == Option::NONE
findLastByte(&self, predicate: fn(u8) -> boolean): Option<u64>

Returns the byte index of the last byte matching predicate, or Option::NONE if no byte matches.

Scans the string from back to front.

Arguments

  • predicate - Callback that receives a byte and returns true if it matches the search criterion.

Example

import String from "std::string";
import Option from "std::option";

let s: String = String::create("/usr/local/bin");

// Find the last slash (byte 47)
let idx: Option<u64> = s.findLastByte((b: u8): boolean -> { return b == 47; });
// idx == Option::SOME(10)

// Find a byte that doesn't exist
let missing: Option<u64> = s.findLastByte((b: u8): boolean -> { return b == 0; });
// missing == Option::NONE
forEach(&self, callback: fn(char) -> void): void

Calls callback once for each decoded UTF-8 scalar.

Invalid UTF-8 bytes are surfaced as U+FFFD and consume one byte so the traversal always makes forward progress.

forEachByte(&self, callback: fn(u8) -> void): void

Calls callback once for each byte in the string, in order.

The callback receives the raw byte value (u8), not a character reference. This is useful for byte-level inspection or accumulation.

Arguments

  • callback - Function invoked with each byte value.

Example

import String from "std::string";

let s: String = String::create("abc");

// Print each byte value (97, 98, 99)
s.forEachByte((b: u8): void -> {
  Io::println(b);
});
forEachChar(&self, callback: fn(char) -> void): void

Compatibility alias for forEach().

hash(&self, hasher: &mut Hasher): void
indexOf(&self, needle: &String): Option<u64>

Returns the byte index of the first occurrence of needle, or NONE.

Arguments

  • needle - The substring to search for.

Returns

Option::SOME(index) with the byte offset of the first match, or Option::NONE if needle is not found.

Example

import String from "std::string";
import Option from "std::option";

let s: String = String::create("hello world");
let needle: String = String::create("world");
let idx: Option<u64> = s.indexOf(&needle);  // Option::SOME(6)
indexOfCharFrom(&self, target: char, startByte: u64): Option<u64>

Returns the byte offset of target at or after startByte.

length(&self): u64

Returns the string length in bytes.

Example

import String from "std::string";

let s: String = String::create("hello");
let len: u64 = s.length();  // 5
lines(&self): Vector<String>

Splits the string into owned lines without the trailing \n byte.

map(&self, mapper: fn(char) -> char): String

Maps each decoded UTF-8 scalar through mapper and returns a new String.

Invalid UTF-8 bytes are surfaced to mapper as U+FFFD and consume one byte in the source string.

mapBytes(&self, mapper: fn(u8) -> u8): String

Maps each byte through mapper and returns a new String.

push(&mut self, other: &String): void

Appends the contents of another String to this string.

Arguments

  • other - The String whose bytes are appended.

Example

import String from "std::string";

let mut s: String = String::create("hello");
let suffix: String = String::create(" world");
s.push(&suffix);
// s is now "hello world"
pushByte(&mut self, c: u8): void

Appends a raw byte to the end of this string.

pushChar(&mut self, c: char): void

Appends one Unicode scalar to the end of this string.

May trigger a reallocation if the buffer is full.

Arguments

  • c - The Unicode scalar to append.

Example

import String from "std::string";

let mut s: String = String::create("ab");
s.pushChar('c');  // s is now "abc"
pushRepeated(&mut self, text: str, count: u64): void

Appends text count times.

pushStr(&mut self, s: str): void

Appends a str literal to the end of this string.

Arguments

  • s - The str slice to append.

Example

import String from "std::string";

let mut s: String = String::new();
s.pushStr("hello");
s.pushStr(" world");
// s is now "hello world"
reserve(&mut self, additional: u64): void

Ensures the buffer has room for at least additional more bytes beyond the current length.

If the current capacity is already sufficient, this is a no-op. Otherwise the buffer is reallocated.

Arguments

  • additional - Number of extra bytes to guarantee.

Example

import String from "std::string";

let mut s: String = String::new();
s.reserve(1024);
// s can now hold at least 1024 bytes without reallocation
split(&self, predicate: fn(u8) -> boolean): Vector<String>

Splits the string at each byte where predicate returns true.

The matching (separator) bytes are not included in the resulting segments. Adjacent separators produce empty strings. A trailing separator produces a trailing empty string.

Arguments

  • predicate - Callback that receives a byte and returns true at split points.

Returns

A Vector<String> of segments. The caller owns the vector and all the strings within it.

How It Works

  "a,b,,c"  with predicate: b == 44 (comma)

   a , b , , c
   ^         ← segment "a"
     ^       ← split
       ^     ← segment "b"
         ^   ← split
           ^ ← split (empty segment "")
             ^← segment "c"

  Result: ["a", "b", "", "c"]

Example

import String from "std::string";
import Vector from "std::vector";

let csv: String = String::create("alice,bob,charlie");

// Split on commas (byte 44)
let parts: Vector<String> = csv.split((b: u8): boolean -> {
  return b == 44;
});
// parts: ["alice", "bob", "charlie"]

// Split on spaces
let words: String = String::create("hello world foo");
let tokens: Vector<String> = words.split((b: u8): boolean -> {
  return b == 32;
});
// tokens: ["hello", "world", "foo"]
startsWith(&self, prefix: str): boolean

Returns whether this string starts with the NUL-terminated prefix bytes.

substring(&self, start: i64, length: i64): String

Returns a new string containing length bytes starting at byte offset start.

Arguments

  • start - Zero-based byte offset to begin extraction.
  • length - Number of bytes to extract.

Example

import String from "std::string";

let s: String = String::create("hello world");
let sub: String = s.substring(6, 5);  // "world"
substringBytes(&self, startByte: u64, endByte: u64): String

Returns a new string containing bytes in startByte..endByte.

This is an owned substring. It does not return a borrowed str, because primitive str is NUL-terminated and does not carry an explicit length.

toBytes(&self): Vector<u8>

Copies the underlying bytes into a new Vector<u8>.

toChars(&self): Vector<char>

Copies the underlying bytes into a new Vector<char>.

This is a byte-oriented compatibility helper: each raw byte is cast to char directly without UTF-8 decoding. Use charAt() with byte offsets when you need Unicode scalar traversal.

toLowerCase(&self): String

Returns a new string with all ASCII uppercase bytes converted to lowercase. Non-ASCII bytes are copied unchanged.

Example

import String from "std::string";

let s: String = String::create("Hello");
let lower: String = s.toLowerCase();  // "hello"
toStr(&self): str

Returns an immutable str view (C const char*) of this string’s data.

toStr() is a zero-copy interop view. The returned str borrows the String’s buffer and is valid as long as the String is alive and not mutated, but C-string consumers may stop at the first interior NUL byte.

Example

import String from "std::string";

let s: String = String::create("hello");
let view: str = s.toStr();  // "hello"
toUpperCase(&self): String

Returns a new string with all ASCII lowercase bytes converted to uppercase. Non-ASCII bytes are copied unchanged.

Example

import String from "std::string";

let s: String = String::create("Hello");
let upper: String = s.toUpperCase();  // "HELLO"
toVectorChars(&self): Vector<char>

Compatibility alias for toChars().

trimAsciiWhitespace(&self): String

Returns a copy with leading and trailing ASCII whitespace removed.

This trims space, line feed, carriage return, and tab. It is intentionally ASCII-only for parsers and compatibility fixture processing.

trimWhere(&self, predicate: fn(u8) -> boolean): String

Returns a new string with leading and trailing bytes stripped where predicate returns true.

Scans inward from both ends, removing bytes as long as the predicate matches. Returns an empty string if all bytes are stripped.

Arguments

  • predicate - Callback that receives a byte and returns true to strip it.

Returns

A new String with the outer matching bytes removed. The original string is unchanged.

How It Works

  "  hello  "  with predicate: b == 32 (space)

  Step 1 — scan from left:
  [ ][  ][h][e][l][l][o][ ][ ]
   ^  ^                          ← strip (space)
         ^                       ← stop (not space)
  start = 2

  Step 2 — scan from right:
  [ ][ ][h][e][l][l][o][ ][ ]
                         ^  ^   ← strip (space)
                      ^         ← stop (not space)
  end = 6

  Result: substring(2, 5) → "hello"

Example

import String from "std::string";

let s: String = String::create("  hello  ");

// Trim spaces (byte 32)
let trimmed: String = s.trimWhere((b: u8): boolean -> { return b == 32; });
// trimmed == "hello"

// Trim tabs and spaces
let mixed: String = String::create("\t hello \t");
let clean: String = mixed.trimWhere((b: u8): boolean -> {
  return b == 32 || b == 9;
});
// clean == "hello"

Functions

function asBytes(value: str): u8[]

Borrows a NUL-terminated str as a non-owning byte slice.

Because primitive str does not carry a length in v0.4, this scans until the first NUL byte. Bytes after an interior NUL are not part of the slice.

function stringAsciiWhitespace(value: char): boolean
function toChars(value: str): Vector<char>

Copies a NUL-terminated str into an owned Vector<char>.

This remains byte-oriented: each byte is cast to char directly and the scan stops at the first terminating NUL.

function toString(value: str): String

Copies a borrowed str into an owned String.

function toString(value: str): String

Copies a borrowed str into an owned String.

function toString(value: str): String

Copies a borrowed str into an owned String.

function toString(value: str): String

Copies a borrowed str into an owned String.

function toString(value: str): String

Copies a borrowed str into an owned String.

function toString(value: str): String

Copies a borrowed str into an owned String.

function toString(value: str): String

Copies a borrowed str into an owned String.

function toString(value: str): String

Copies a borrowed str into an owned String.

function toString(value: str): String

Copies a borrowed str into an owned String.

function toString(value: str): String

Copies a borrowed str into an owned String.

function toString(value: str): String

Copies a borrowed str into an owned String.

function toString(value: str): String

Copies a borrowed str into an owned String.

function toString(value: str): String

Copies a borrowed str into an owned String.