Under heavy development. This site describes the intended 1.0 language. Current features and performance vary. Development status
Sn50SnThe SnAPI programming language
Development · Evolving reference

str

Borrowed immutable UTF-8 text with byte-length semantics.

Sn is under heavy development. This reference includes the intended 1.0 design; features may be incomplete in the current compiler. Read the status →

These examples describe the Development language contract. They are not a claim that every workflow is supported by the current public preview. Declaration excerpts belong inside a function unless a full module is shown.

Value and lifetime

str is an immutable, non-owning pointer-and-byte-length view of valid UTF-8. It has no numeric min/max range. A nonempty view must point to a live readable range of its byte length; an empty view may have a null or nonnull data pointer and permits no byte read. The view does not retain storage. A backing owner’s destruction, reallocation or relevant mutation can invalidate it.

let Literal: str = "static storage";
let Empty: str = "";
let Bytes: usize = Literal.ByteLength;

Bytes, scalars and termination

Data projects readonly byte*; ByteLength is usize and counts UTF-8 bytes, not scalars or grapheme clusters. Projection is safe and allocation-free; raw pointer use has its own safety requirements. A str is not required to have a NUL terminator. Use a C string at an API that requires one.

let Text: str = "λ";
let Bytes: usize = Text.ByteLength; // 2
let Pointer: readonly byte* = Text.Data;
let Raw: readonly byte[] = Text; // Same borrowed bytes.

Validation and ownership

str implicitly weakens to readonly byte[] without copying; reverse conversion must validate UTF-8. str.TryViewUtf8 returns str? and borrows the same storage. String is an owning library type; a stored String’s .View borrows it, and must not outlive it. Ordinary literals are static str values.

let Bytes: readonly byte[] = "valid UTF-8";
let Text: str? = str.TryViewUtf8(Bytes);
let Label: str = "Sn";

See all primitive types and literals and user-defined suffixes.

Make your own type Formattable

A custom type supplies a visible free FormatTo overload with the canonical signature: Value: readonly YourType&, Builder: mut StringBuilder&, result void. Formattable is a structural concept: no base class, runtime registration or ToString method is required. Keep the overload in the type’s module and import it with the type when formatting from another module. Use a namespace import such as import std.format as Formatting; to compose the standard overloads as Formatting.FormatTo. A selected import named FormatTo conflicts with the local custom binding.

This complete Development example defines Point formatting, uses it in runtime interpolation, and prints the result. This complete module passes checking and JIT execution with the published 0.2.0 Preview 1 compiler. It illustrates the accepted contract; it is not a compatibility test for every public compiler version.

module example.custom_format;
import std.format as Formatting;
from std.text import String, StringBuilder;
from std.io import Print;

pub struct Point {
    pub X: i32;
    pub Y: i32;
}

pub const fn FormatTo(Value: readonly Point&, Builder: mut StringBuilder&) -> void {
    Builder.Append("(");
    Formatting.FormatTo(Value.X, Builder);
    Builder.Append(", ");
    Formatting.FormatTo(Value.Y, Builder);
    Builder.Append(")");
}


fn Describe(X: i32, Y: i32) -> String {
    let Value = Point { X = X, Y = Y };
    return $"Position {Value}";
}

fn Main() -> i32 {
    let Position = Point { X = 20, Y = 22 };
    let Description = Describe(Position.X, Position.Y);
    Print(Description.View);
    Print("\n");
    return 0;
}

Expected text:

Position (20, 22)

Static and runtime formatting use the same contract

The custom overload is const fn, so its body is eligible for constant evaluation when all operands and called operations are supported there. Describe is deliberately an ordinary runtime function: its interpolation returns an owning String, and Description.View borrows that owner. The compiler selects FormatTo at compile time without adding a runtime formatting interface.

A separate module shows primitive formatting that folds to static str storage. Both the custom runtime example above and this static example pass the published 0.2.0 Preview 1 compiler. This does not promise that every user-defined aggregate is supported in required constant evaluation. A non-const formatter may serve runtime interpolation but cannot satisfy a required constant result.

module example.static_format;
from std.format import FormatTo;
const X: i32 = 20;
const Y: i32 = 22;
const Label: str = $"Position ({X}, {Y})";
fn Main() -> i32 {
    if Label != "Position (20, 22)" { return 1; }
    return 0;
}

Build formatted text without interpolation

Call the same overload explicitly into a StringBuilder. Append and void-returning FormatTo share sticky allocation-failure state; TryFinalize exposes recoverable finalization instead of interpolation’s fatal-on-failure path. The custom formatter appends to the supplied builder, does not retain it, and does not make a separate owning String for every field.

// Inside a function, with Point and its FormatTo overload in scope:
var Builder = StringBuilder();
let Position = Point { X = 20, Y = 22 };
FormatTo(Position, Builder);
switch Builder.TryFinalize() {
    case .Ok { Value = Text } => { Print(Text.View); }
    case .Err { Error = _ } => { return 1; }
}
Documentation snapshot: October 24, 2026 About versions ↗
Searching Development · language & standard library