Skip to content
CryoCryo home
Stdlibcore

primitives

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

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.

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;
    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;
    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;
    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:

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;

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

Floats

implement f64 {
    abs(&this) -> f64;
    is_nan(&this) -> boolean;
}

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.

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.

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.