Literals & user-defined literals
Token spellings, type inference and compile-time literal providers.
Literal syntax controls both the value and its type. Examples here target the Development contract; availability depends on the selected compiler version.
Integer literals and inference
Integer tokens are arbitrary-precision compile-time values until contextual typing. Decimal, binary (0b), octal (0o) and hexadecimal (0x) prefixes are supported; radix prefixes are case-insensitive. Without a type context an integer literal defaults to i32. A destination or explicit suffix must represent its value. A leading sign is a unary operator; signed-minimum checking happens after directly applied negation.
let Default = 42; // i32
let Octet: u8 = 255;
let Binary = 0b1010_0110_u8;
let Permissions = 0o755_usize;
let Hex = 0XFFFF_FFFF_u32;
let Minimum = -128_i8;
// Rejected: let Outside = 256_u8;Separators and built-in suffixes
Underscores separate digits only between digits of the same radix. Leading, trailing and repeated digit separators are rejected. An exact lowercase built-in suffix requires its separating underscore. Integer suffixes are _i8 / _i16 / _i32 / _i64 / _i128, _u8 / _u16 / _u32 / _u64 / _u128, _isize / _usize and _byte. Float suffixes are _f16 / _f32 / _f64. See individual primitive ranges.
let Count = 1_000_000_i64;
let Mask = 0xAB_CD_u16;
let Raw = 255_byte;
let Half = 1_f16; // Floating suffix makes this a real token.
// Rejected: 1__000, 100_, 42U, 1.0fFloating literals
Floating tokens are digits-dot-digits with an optional decimal exponent, digits with a required decimal exponent, or digits with a floating suffix. Unconstrained values default to f64; a fitting contextual value can select f16/f32. Destination IEEE rounding is nearest, ties to even. Finite out-of-range literals diagnose. Hex floats are outside this grammar; write 0.5 and 1.0 rather than .5 and 1.
let Default = 0.5; // f64
let Single: f32 = 0.5;
let Half = 1.0_f16;
let Exponent = 1.25e+2_f64;
let IntegerSpelling = 1_f32;
// Rejected: .5, 1., 0x1.fp2Boolean, absence and null spellings
true and false are bool literals. none denotes optional absence in a suitable expected type; null denotes a raw null pointer. They are different domains and do not imply integer/pointer truthiness. A null pointer is not a valid borrowed reference.
let Enabled: bool = true;
let Missing: i32? = none;
let Pointer: readonly byte* = null;
let IsNull: bool = Pointer == null;Character and byte literals
A char literal in single quotes decodes to exactly one Unicode scalar, excluding U+D800–U+DFFF and values beyond U+10FFFF. A b'...' byte literal accepts one ASCII byte or byte escape, not a Unicode escape. Applicable escapes include \\, \', \", \0, \n, \r, \t, exactly two hex digits in \xHH, and one through six digits in \u{H...}. Two scalars do not become one char merely because they look like one grapheme.
let Scalar: char = 'λ';
let Emoji: char = '\u{1F642}';
let Newline: char = '\n';
let Ascii: byte = b'A';
let Octet: byte = b'\xFF';
// Rejected: '\u{D800}', 'ab', b'λ', b'\u{41}'String and C-string literals
Normal double-quoted literals are static, immutable UTF-8 str views and do not allocate. They do not normalize Unicode or promise a terminator through the str contract. c"..." instead supplies static read-only NUL-terminated byte storage; decoded interior NUL is rejected. Ordinary string escapes must produce valid UTF-8. Raw/multiline/byte-string forms are not part of this literal contract.
let Text: str = "Hello\nSn";
let Bytes: usize = "λ".ByteLength; // 2
let Native: readonly byte* = c"native API";
// Rejected: c"interior\0NUL", b"bytes", r"raw"Interpolation and literal braces
Interpolation uses $"...{Expression}...". If all expressions and selected FormatTo operations are constant-evaluable, the result is a static str. Otherwise the result is an owning String and can allocate. Runtime interpolation fatal-traps on building failure; it does not return Result. Double braces emit literal braces. Format specifiers are not silently inherited from C#, Python or C++. The custom formatter below shows how to control a user-defined type’s text.
const Name: str = "Ada";
const Label: str = $"Player {Name}";
const Braces: str = $"{{{Name}}}"; // {Ada}
// Custom Formattable implementation and ownership example follow below.User-defined numeric suffixes
A user-defined numeric literal is an ordinary value constructed by a declaration-backed consteval provider. The provider receives exact suffix-free token text as str, not a pre-rounded integer/float or a runtime string parser invocation. It may validate a domain and return a fixed user-defined type. This pixel provider accepts nonnegative decimal i64 counts. TryParse deliberately rejects radix prefixes and separators, so 0x280_px and 6_40_px fail in this example even though built-in numeric grammar can lex them. A different provider can implement those spellings explicitly.
module example.pixel_literals;
from std.compiler import LiteralSuffix;
from std.text import TryParse;
pub struct Pixels { pub Count: i64; }
[LiteralSuffix("px")]
[LiteralSuffix("pixels")]
pub consteval fn ParsePixels(Text: str) -> Pixels {
switch TryParse<i64>(Text) {
case .Ok { Value = Count } => {
if Count < 0 { panic; }
return Pixels { Count = Count };
}
case .Err { Error = _ } => { panic; }
}
}
fn Main() -> i32 {
let Width: Pixels = 640_px;
let Height = 480_pixels;
if Width.Count != 640 || Height.Count != 480 { return 1; }
return 0;
}Make a provider visible
A provider is visible in its own module, via a direct namespace import of its module, or through an explicitly selected import of that provider declaration. An alias does not rename the suffix. There is no global package scan, runtime registration, import-order preference or transitive discovery. Two distinct visible providers for the same suffix/token kind diagnose; multiple imports of the same provider do not create ambiguity.
module example.window;
import example.pixel_literals as Pixels;
fn Main() -> i32 {
let Width = 640_px;
return Width.Count as i32;
}Integer versus real providers
LiteralSuffix("name") selects lexical integer tokens; LiteralSuffix("name", Real = true) selects lexical real tokens. The suffix excludes its leading underscore. One spelling can have an integer provider and a real provider, selected by token syntax, not by expected type. Each provider is a public safe top-level consteval function with exactly one by-value str parameter, no generics/variadics/async/foreign binding, and a fixed result other than void/never. Built-in suffix names are reserved. Suffix names begin with an ASCII letter followed by letters, digits or underscores.
// Additional real provider in the example.pixel_literals module:
[LiteralSuffix("px", Real = true)]
pub consteval fn ParseFractionalPixels(Text: str) -> f64 {
switch TryParse<f64>(Text) {
case .Ok { Value = Count } => {
if Count < 0.0_f64 { panic; }
return Count;
}
case .Err { Error = _ } => { panic; }
}
}
// 640_px uses ParsePixels; 0.5_px uses ParseFractionalPixels.
// These intentionally have different result types.Signs, range failures and evaluation
A directly applied unary sign is folded into the provider’s input before range validation. Parenthesized negation acts on the provider’s result instead. Provider rejection and required consteval failure are compile-time diagnostics. There is no runtime parsing fallback. Input parsing, range policy, return type and consteval work limits remain explicit; a suffix does not add arithmetic, conversions or formatting to the returned type.
// With ParsePixels above:
let Good = +42_px; // Provider receives "+42".
// Rejected: -42_px passes "-42"; provider rejects negative pixels.
// -(42_px) applies unary minus to Pixels, which defines no such operator.
// Rejected: 999999999999999999999999999999_px overflows i64 parsing.Standard duration literals
The standard time module uses the same provider mechanism for durations. Integer tokens use i64 counts; real tokens use finite f64 counts. Units are ordinary type information and the resulting values use duration operations. These examples describe current Development source, not a new guarantee for the 0.2.0 preview. See the selected toolchain’s release notes for support.
module example.duration_literals;
import std.time as Time;
fn Main() -> i32 {
let Budget = 2_s + 250_ms; // 2250 milliseconds
let Fraction = 0.5_ms; // Floating duration count
let Longer = 1_days;
if Budget.Count != 2250 { return 1; }
return 0;
}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; }
}