Skip to content
CryoCryo home
Stdlibcore

primitives

import std::core::primitives; — in the prelude · source

Types

Methods on primitive types

implement blocks can extend the built-in types, and core::primitives uses that sparingly — only where the operators do not already say it in one line. All of it is in the prelude.

Every numeric width carries static min_value() and static max_value(). The signed types carry abs; f32 and f64 carry abs and is_nan.

char

implement char {
    is_digit(&this) -> boolean;
    is_alpha(&this) -> boolean;
    is_alphanumeric(&this) -> boolean;
    is_hex_digit(&this) -> boolean;
    is_whitespace(&this) -> boolean;
    is_ascii(&this) -> boolean;
    to_ascii_lowercase(&this) -> char;
    to_ascii_uppercase(&this) -> char;
    hex_value(&this) -> i32;
    is_c_ident_start(&this) -> boolean;
    is_c_ident_char(&this) -> boolean;
}

The ASCII classifiers, plus to_ascii_lowercase, to_ascii_uppercase, and hex_value (the digit's value, or -1 when it isn't one). Case folding is ASCII-only; full Unicode folding needs a table beyond this module.

Methods
is_digit(&this) -> boolean;
is_alpha(&this) -> boolean;
is_alphanumeric(&this) -> boolean;
is_hex_digit(&this) -> boolean;

ASCII hexadecimal digit: 0-9, a-f, or A-F.

is_whitespace(&this) -> boolean;
is_ascii(&this) -> boolean;

True when the scalar is in the 7-bit ASCII range (0..=127).

to_ascii_lowercase(&this) -> char;

ASCII fold to lowercase. Non-ASCII scalars pass through unchanged; full Unicode case folding requires a table beyond this module.

to_ascii_uppercase(&this) -> char;

ASCII fold to uppercase. Non-ASCII scalars pass through unchanged.

hex_value(&this) -> i32;

Numeric value of a hex digit (0-9, a-f, A-F), or -1 when this is not a hex digit. Consolidates the nibble-decode loops that the compiler hand-rolls in literal/char parsing.

is_c_ident_start(&this) -> boolean;

True when this may begin a C identifier: an ASCII letter or _.

is_c_ident_char(&this) -> boolean;

True when this may continue a C identifier: letter, digit, or _.

Integers

Every signed width — i8, i16, i32, i64, i128, isize — has the same shape as i32:

implement i32 {
    static min_value() -> i32;
    static max_value() -> i32;
    abs(&this) -> i32;
    checked_abs(&this) -> Option<i32>;
    clamp(&this, lo: i32, hi: i32) -> i32;
    to_decimal_buf(&this, buffer: u8*) -> u64;
}

Every unsigned width — u8, u16, u32, u64, u128, usize — has the shape of u32:

implement u32 {
    static min_value() -> u32;
    static max_value() -> u32;
    clamp(&this, lo: u32, hi: u32) -> u32;
    to_decimal_buf(&this, buffer: u8*) -> u64;
    to_hex_buf(&this, buffer: u8*) -> u64;
    to_hex_upper_buf(&this, buffer: u8*) -> u64;
    to_oct_buf(&this, buffer: u8*) -> u64;
    to_bin_buf(&this, buffer: u8*) -> u64;
}

u64 additionally carries the padded and allocating forms:

implement u64 {
    static min_value() -> u64;
    static max_value() -> u64;
    clamp(&this, lo: u64, hi: u64) -> u64;
    to_decimal_buf(&this, buffer: u8*) -> u64;
    to_decimal_str(&this) -> string;
    to_hex_buf(&this, buffer: u8*) -> u64;
    to_hex_upper_buf(&this, buffer: u8*) -> u64;
    to_hex_padded_buf(&this, buffer: u8*, width: u64) -> u64;
    to_hex_padded_str(&this, width: u64) -> string;
    to_hex_padded_upper_buf(&this, buffer: u8*, width: u64) -> u64;
    static to_hex_padded_buf_with_table(value: u64, buffer: u8*, width: u64, digits_table: u8*) -> u64;
    to_oct_buf(&this, buffer: u8*) -> u64;
    to_bin_buf(&this, buffer: u8*) -> u64;
    to_decimal_padded_buf(&this, buffer: u8*, width: u64) -> u64;
}

Integer-to-text conversion lives here as buffer-writing methods rather than allocating ones: to_decimal_buf, and on the unsigned types also to_hex_buf, to_hex_upper_buf, to_oct_buf, and to_bin_buf. Each writes into a caller-owned u8* and returns the byte count. No heap traffic — the digit loop runs in a stack scratch and copies once. Size the buffer with the matching constant.

to_decimal_str on the 64-bit and wider types is the allocating form, returning a fresh C string the caller owns.

i32
Constructors

####### static min_value() -> i32;

####### static max_value() -> i32;

Methods

####### abs(&this) -> i32;

Wraps at i32::min_value(): the most-negative value has no positive counterpart in two's complement, so i32::min_value().abs() is itself; checked_abs answers None there.

####### checked_abs(&this) -> Option<i32>;

Some(|this|), or None for min_value(), whose magnitude is not representable: only it negates to a value still negative.

####### clamp(&this, lo: i32, hi: i32) -> i32;

this confined to [lo, hi].

####### to_decimal_buf(&this, buffer: u8*) -> u64;

Buffer must hold at least I32_MAX_DECIMAL_DIGITS bytes.

u32
Constructors

####### static min_value() -> u32;

####### static max_value() -> u32;

Methods

####### clamp(&this, lo: u32, hi: u32) -> u32;

this confined to [lo, hi].

####### to_decimal_buf(&this, buffer: u8*) -> u64;

####### to_hex_buf(&this, buffer: u8*) -> u64;

####### to_hex_upper_buf(&this, buffer: u8*) -> u64;

####### to_oct_buf(&this, buffer: u8*) -> u64;

####### to_bin_buf(&this, buffer: u8*) -> u64;

u64
Constructors

####### static min_value() -> u64;

####### static max_value() -> u64;

####### static to_hex_padded_buf_with_table(value: u64, buffer: u8*, width: u64, digits_table: u8*) -> u64;

Methods

####### clamp(&this, lo: u64, hi: u64) -> u64;

this confined to [lo, hi].

####### to_decimal_buf(&this, buffer: u8*) -> u64;

Write this in base 10 into buffer. Returns bytes written. buffer must hold at least U64_MAX_DECIMAL_DIGITS bytes. Builds the digits on a 20-byte stack scratch then copies in one shot; no heap traffic.

####### to_decimal_str(&this) -> string;

Decimal text on a fresh heap buffer (caller owns), mirroring C's "%llu". For a no-alloc path, write into your own buffer with to_decimal_buf.

####### to_hex_buf(&this, buffer: u8*) -> u64;

Lowercase hex. Returns bytes written. buffer must hold at least U64_MAX_HEX_DIGITS bytes.

####### to_hex_upper_buf(&this, buffer: u8*) -> u64;

Uppercase variant.

####### to_hex_padded_buf(&this, buffer: u8*, width: u64) -> u64;

Zero-padded fixed-width hex. Writes exactly width characters, truncating high nibbles if the value doesn't fit. Useful for pointer formatting (width = 16), hash dumps, binary protocols.

####### to_hex_padded_str(&this, width: u64) -> string;

Zero-padded fixed-width lowercase hex on a fresh heap buffer (caller owns): exactly width characters plus a NUL. width = 16 renders a full u64 (the pointer/hash-dump convention).

####### to_hex_padded_upper_buf(&this, buffer: u8*, width: u64) -> u64;

####### to_oct_buf(&this, buffer: u8*) -> u64;

Base 8. buffer must hold at least U64_MAX_OCT_DIGITS bytes.

####### to_bin_buf(&this, buffer: u8*) -> u64;

Base 2. buffer must hold at least U64_MAX_BIN_DIGITS bytes.

####### to_decimal_padded_buf(&this, buffer: u8*, width: u64) -> u64;

Zero-padded fixed-width decimal. Writes exactly width digits, truncating the high end if the value doesn't fit. Used for the fractional piece of a float where the digit count is known ahead of time.

Floats

implement f64 {
    abs(&this) -> f64;
    clamp(&this, lo: f64, hi: f64) -> f64;
    is_nan(&this) -> boolean;
    sqrt(&this) -> f64;
}

f32 has the same two methods. Float formatting is not here — it needs libm, and keeping libc out of core matters more. It lives in fmt::float.

Methods
abs(&this) -> f64;

Absolute value by clearing the sign bit, so -0.0 gives 0.0 and a NaN keeps its payload with its sign cleared.

clamp(&this, lo: f64, hi: f64) -> f64;

this confined to [lo, hi], by plain comparisons: a NaN this compares false against both bounds and is returned unchanged.

is_nan(&this) -> boolean;

NaN != NaN, so the cheapest portable check is self-comparison.

sqrt(&this) -> f64;

Square root: NaN for a negative this.

Bridge methods on string

implement string {
    length(&this) -> u64;
    is_empty(&this) -> boolean;
    is_text_literal(&this) -> boolean;
    is_float_literal(&this) -> boolean;
    is_unsigned_literal(&this) -> boolean;
    substring(&this, start: u32, len: u32) -> string;
    substr(&this, start: i64, len: i64) -> string;
    append(&this, other: string) -> string;
    trim(&this) -> string;
    strip_prefix(&this, prefix: string) -> string;
    ends_with(&this, suffix: string) -> boolean;
    common_prefix_len(&this, other: string) -> i64;
    contains(&this, needle: string) -> boolean;
    find_char(&this, ch: i8) -> i64;
    rfind_char(&this, ch: i8) -> i64;
    parse_int<T>(&this) -> T;
    parse_c_uint(&this) -> u64;
    strip_quotes(&this) -> string;
    parent_dir(&this) -> string;
    eq(&this, other: string) -> boolean;
    starts_with(&this, prefix: string) -> boolean;
    find(&this, needle: string) -> i64;
    rfind(&this, needle: string) -> i64;
    split_char(&this, sep: i8) -> [string];
    split(&this, sep: string) -> [string];
    split_nonempty(&this, sep: string) -> [string];
    replace(&this, from: string, to: string) -> string;
    to_ascii_lower(&this) -> string;
    to_ascii_upper(&this) -> string;
    basename(&this) -> string;
    stem(&this) -> string;
    extension(&this) -> string;
    join(&this, seg: string) -> string;
    is_under(&this, dir: string) -> boolean;
    with_exe_suffix(&this, suffix: string) -> string;
}

The raw string type — a NUL-terminated C view — carries a set of convenience methods so the self-hosted compiler, which predates Str and String, keeps building without a rewrite. Every method that produces text allocates a fresh C string and leaks it to the caller. New code should prefer Str and String; these exist for the bridge.

Methods
length(&this) -> u64;
is_empty(&this) -> boolean;
is_text_literal(&this) -> boolean;

True when this token spelling is a C string or character literal ("...", 'c', or an encoding-prefixed form: L"...", u'...', U"...", u8"...", R"(...)"). Detected by the presence of a " or ' quote: a well-formed C numeric literal never contains one, so this correctly accepts the prefixed forms too - a prefix-scan that bailed on the first digit would misread the 8 in u8"..." as a numeric literal.

is_float_literal(&this) -> boolean;

True when this numeric-literal token spelling is a C floating-point constant. A decimal float carries a . or a decimal exponent (e/E); a hexadecimal float (0x...) carries a . or a binary exponent (p/P). The hex case is handled distinctly because in hex the letters e/E are digits, not an exponent - and p/P is the definitive float marker, so a dot-less hex float such as 0x1p4 is correctly recognized (a ./e only scan would miss it and mis-bind it as an integer). Assumes a numeric literal - rule out string/char literals with is_text_literal first.

is_unsigned_literal(&this) -> boolean;

True when this integer-literal token spelling carries an unsigned type suffix (u/U). Only the trailing suffix run is inspected, stepping over any l/L length suffix, so 5u, 0xFFul, 10LU, and 7llu all report unsigned while 5, 5l, and 7ll do not.

substring(&this, start: u32, len: u32) -> string;

substring(start, len): extract len bytes starting at start, returning a freshly allocated NUL-terminated string. Matches the legacy compiler's call convention (start, length), not (start, end).

substr(&this, start: i64, len: i64) -> string;

Signed-offset slice: like substring but robust to the computed ranges cstring-style code produces. A non-positive len (or an out-of-range start) yields ""; an over-long len is clamped to the remaining bytes, so callers never need to bounds-check first. It reads no further than start + len: a tokenizer slicing a token off the rest of a file pays for the token, not for the rest of the file.

append(&this, other: string) -> string;

Concatenate other onto a fresh heap-allocated copy of this, returning the new NUL-terminated string. Caller owns the result.

trim(&this) -> string;

Trim leading and trailing ASCII whitespace (space, tab, LF, CR), returning a freshly allocated string ("" when all whitespace).

strip_prefix(&this, prefix: string) -> string;

If this begins with prefix, return the remainder as a fresh string; otherwise return this unchanged (same buffer).

ends_with(&this, suffix: string) -> boolean;

True when this ends with suffix (byte-wise). An empty suffix always matches; a suffix longer than this never does.

common_prefix_len(&this, other: string) -> i64;

Length, in bytes, of the longest common leading prefix of this and other. Useful for ranking candidates by shared-path overlap.

contains(&this, needle: string) -> boolean;

True when needle occurs anywhere in this (substring search). An empty needle always matches, mirroring C strstr.

find_char(&this, ch: i8) -> i64;

Byte index of the first occurrence of ch, or -1 if absent.

rfind_char(&this, ch: i8) -> i64;

Byte index of the last occurrence of ch, or -1 if absent.

parse_int<T>(&this) -> T;

Parse this into an integer of the caller-chosen type T. Recognises an optional leading sign (signed T only), a 0x/0o/0b radix prefix, and _ digit separators; parsing stops at the first character invalid for the detected base. T is dispatched at compile time via static match, so each instantiation is exactly the right-width parser: lexeme.parse_int<u64>(), version.parse_int<i64>().

parse_c_uint(&this) -> u64;

Parse this as a C integer literal with strtoull(s, null, 0) semantics: an optional sign, then 0x/0X hex, 0b/0B binary, 0o/0O or a bare leading 0 octal, else decimal; _ separators are skipped and parsing stops at the first character invalid for the detected base (e.g. a U/L type suffix). Unlike parse_int, a bare leading 0 selects octal - matching C header #defines like 0755. INFALLIBLE: malformed input yields 0 and overflow wraps.

strip_quotes(&this) -> string;

Strip one pair of surrounding ASCII double quotes if both are present; otherwise return this unchanged (same buffer).

parent_dir(&this) -> string;

The directory portion of a path: everything before the last / or \ separator, or "." when there is no leading directory component.

eq(&this, other: string) -> boolean;

Byte-wise content equality, taking other BY VALUE so it accepts string literals directly (name.eq("void")). This is the ergonomic replacement for the strcmp(a, b) == 0 idiom; the Eq trait's equals(&string) needs an lvalue and can't take a literal. Two nulls are equal; exactly one null is not.

starts_with(&this, prefix: string) -> boolean;

True when this begins with prefix (byte-wise). An empty prefix always matches; a prefix longer than this never does.

find(&this, needle: string) -> i64;

Byte index of the first occurrence of needle, or -1 when absent. An empty needle matches at 0.

rfind(&this, needle: string) -> i64;

Byte index of the last occurrence of needle, or -1 when absent. An empty needle matches at the end (the string's length).

split_char(&this, sep: i8) -> [string];

Split on a single byte separator, returning each piece as a freshly allocated string. Adjacent separators yield empty pieces; the result always has (separator-count + 1) entries.

split(&this, sep: string) -> [string];

Split on a multi-byte separator string. An empty separator yields the whole string as a single piece. Covers ::-style segmentation.

split_nonempty(&this, sep: string) -> [string];

Like split, but drops empty pieces: leading, trailing, and consecutive separators produce no entry, and empty input yields an empty array. Useful for ::-delimited names and /-delimited paths where blank segments are meaningless.

replace(&this, from: string, to: string) -> string;

Replace every non-overlapping occurrence of from with to, returning a freshly allocated string. An empty from returns this unchanged.

to_ascii_lower(&this) -> string;

ASCII-fold to lowercase, returning a freshly allocated string.

to_ascii_upper(&this) -> string;

ASCII-fold to uppercase, returning a freshly allocated string.

basename(&this) -> string;

The final path component: everything after the last / or \.

stem(&this) -> string;

The file stem: basename with its final .ext removed. A leading-dot name (e.g. .gitignore) is treated as having no extension.

extension(&this) -> string;

The file extension without the dot, or "" when there is none. A leading-dot name (e.g. .gitignore) has no extension.

join(&this, seg: string) -> string;

Join seg onto this with a single / separator. An empty this returns seg; an empty seg returns this; an absolute seg (leading separator) replaces this entirely.

is_under(&this, dir: string) -> boolean;

True when this lies within directory dir: equal to dir, or extending it across a path separator. Boundary-aware so /a/bc is NOT under /a/b.

with_exe_suffix(&this, suffix: string) -> string;

Append suffix unless it is empty or already present at the end. Used to add a platform executable suffix (e.g. .exe) idempotently.

Integer overflow is wrapping

Integer arithmetic at every width is silent two's-complement wrapping on overflow. There is no trap and no diagnostic. This is a deliberate, frozen part of the 1.0 surface: Cryo ships no checked_*, wrapping_*, or saturating_* family, so code that must detect overflow has to range-check its operands first.

One visible consequence is abs: the most-negative value of a signed type has no positive counterpart, so i32::min_value().abs() is itself.

Constants

const U8_MAX_DECIMAL_DIGITS:   u64 = 3;
const U16_MAX_DECIMAL_DIGITS:  u64 = 5;
const U32_MAX_DECIMAL_DIGITS:  u64 = 10;
const U64_MAX_DECIMAL_DIGITS:  u64 = 20;
const U128_MAX_DECIMAL_DIGITS: u64 = 39;
const I8_MAX_DECIMAL_DIGITS:   u64 = 4;
const I16_MAX_DECIMAL_DIGITS:  u64 = 6;
const I32_MAX_DECIMAL_DIGITS:  u64 = 11;
const I64_MAX_DECIMAL_DIGITS:  u64 = 20;
const I128_MAX_DECIMAL_DIGITS: u64 = 40;
const U8_MAX_HEX_DIGITS:       u64 = 2;
const U16_MAX_HEX_DIGITS:      u64 = 4;
const U32_MAX_HEX_DIGITS:      u64 = 8;
const U64_MAX_HEX_DIGITS:      u64 = 16;
const U128_MAX_HEX_DIGITS:     u64 = 32;
const U8_MAX_OCT_DIGITS:       u64 = 3;
const U16_MAX_OCT_DIGITS:      u64 = 6;
const U32_MAX_OCT_DIGITS:      u64 = 11;
const U64_MAX_OCT_DIGITS:      u64 = 22;
const U128_MAX_OCT_DIGITS:     u64 = 43;
const U8_MAX_BIN_DIGITS:       u64 = 8;
const U16_MAX_BIN_DIGITS:      u64 = 16;
const U32_MAX_BIN_DIGITS:      u64 = 32;
const U64_MAX_BIN_DIGITS:      u64 = 64;
const U128_MAX_BIN_DIGITS:     u64 = 128;