Utilities & C Compatibility
Small single-header modules at the top of std/: number parsing and formatting, character classes, errors and signals, time, logging, panics, Result, checked arithmetic, the global heap, DMA buffers, and the C11/C23 compatibility headers.
#include <std/convert.h>
#include <std/time.h>
// ... one header per module, or:
#include <std/prelude.h>
Bit manipulation (bit.h) and complex numbers (complex.h) are documented under math. Fixed-width integer and limit headers (stdint.h, stddef.h, stdbool.h, limits.h, float.h, inttypes.h) only define types and constants; see C Type and Limit Headers below.
Constants are functions
errno.h, fenv.h, locale.h and signal.h expose their constants as functions so they can be reached through the namespace: write std::SIG_INT(), not SIG_INT. Constants in log.h, sys.h, stdckdint.h, heap.h and dma.h are plain #defines.
?&int ok parameters may be null when you don't need the success flag.
convert — String ↔ Number
#include <std/convert.h>
// String -> number. *ok = 1 on success; on failure *ok = 0 and the result is 0.
long long str_to_int(const &char s, ?&int ok);
unsigned long long str_to_uint(const &char s, ?&int ok);
unsigned long long str_to_hex(const &char s, ?&int ok); // "0x" prefix optional
double str_to_float(const &char s, ?&int ok);
int str_is_int(const &char s); // 1 if s is a valid decimal integer
int str_is_float(const &char s); // 1 if s is a valid floating-point number
// Number -> heap string (null on OOM). Free the result with std::dealloc().
?&heap char int_to_str(long long v);
?&heap char uint_to_str(unsigned long long v);
?&heap char float_to_str(double v, int decimals);
// Numeric conversions
int ll_to_int(long long v, &int out); // 1 and *out = v if it fits in int, else 0
long long float_clamp_to_ll(double v, long long min, long long max); // clamp, then round
#include <std/convert.h>
int main() {
int ok = 0;
long long n = std::str_to_int("1234", &ok); // 1234, ok = 1
std::str_to_int("12x", &ok); // ok = 0
unsigned long long h = std::str_to_hex("0xff", null); // 255
return (n == 1234LL && h == 255ULL) ? 0 : 1;
}
Run in Playgroundctype — Character Classes
#include <std/ctype.h>
Locale-aware, like C's <ctype.h>. c must be in 0..255 or -1 (EOF). Each predicate returns non-zero for true. For locale-independent ASCII checks, see Encoding.
| Function | True for |
|---|---|
char_is_alpha(c) |
letter |
char_is_digit(c) |
0–9 |
char_is_alnum(c) |
letter or digit |
char_is_xdigit(c) |
0–9, a–f, A–F |
char_is_space(c) |
space, tab, newline, CR, FF, VT |
char_is_blank(c) |
space or tab |
char_is_print(c) |
printable, including space |
char_is_graph(c) |
printable, excluding space |
char_is_punct(c) |
printable, not alphanumeric, not space |
char_is_ctrl(c) |
control character |
char_is_upper(c) / char_is_lower(c) |
upper / lower case letter |
char_to_lower(c) and char_to_upper(c) return the converted character, or c unchanged.
errno — Error Codes
#include <std/errno.h>
int errno_get(); // current thread-local errno
void errno_set(int code);
const &char errno_str(int code); // static description (strerror)
void errno_print(const &char prefix); // "prefix: <description>" to stderr
Constants (call them): ERRNO_OK(), ERRNO_EPERM(), ERRNO_ENOENT(), ERRNO_EIO(), ERRNO_EBADF(), ERRNO_ENOMEM(), ERRNO_EACCES(), ERRNO_EBUSY(), ERRNO_EEXIST(), ERRNO_EINVAL(), ERRNO_ERANGE(), ERRNO_EAGAIN(), ERRNO_EWOULDBLOCK(), ERRNO_ETIMEDOUT().
Most codes have the same value on Linux, macOS and BSD. EAGAIN/EWOULDBLOCK/ETIMEDOUT differ (Linux 11/11/110, macOS/BSD 35/35/60), and Windows sockets don't report through errno at all. To ask whether a non-blocking socket call would have blocked, use std::sock_would_block() from sched/io_nb.h instead of comparing errno_get().
fenv — Floating-Point Environment
#include <std/fenv.h>
int fenv_clear(int excepts); // clear flags (FE_ALL_EXCEPT() clears all)
int fenv_test(int excepts); // which of these flags are raised
int fenv_raise(int excepts);
int fenv_get_round(); // FE_* rounding mode
int fenv_set_round(int mode); // 0 on success
// Whole environment, saved into / restored from a 32-byte buffer.
int fenv_save(&unsigned char env);
int fenv_restore(const &unsigned char env);
int fenv_save_clear(&unsigned char env); // save, then clear all flags
| Rounding modes | Exception flags |
|---|---|
FE_TONEAREST(), FE_DOWNWARD(), FE_UPWARD(), FE_TOWARDZERO() |
FE_INVALID(), FE_DIVBYZERO(), FE_OVERFLOW(), FE_UNDERFLOW(), FE_INEXACT(), FE_ALL_EXCEPT() |
The values are the target's own <fenv.h> values, which differ per architecture (x86, ARM/AArch64 and RISC-V each have their own); always pass the functions, never literal numbers.
fmt — Formatting into Buffers
#include <std/fmt.h>
Each function writes into buf[0 .. cap-1], always NUL-terminates, and returns the characters written (not counting NUL), or -1 on error.
int fmt_int([]char buf, long long v);
int fmt_uint([]char buf, unsigned long long v);
int fmt_float([]char buf, double v, int decimals);
int fmt_hex([]char buf, unsigned long long v); // lowercase
int fmt_hex_upper([]char buf, unsigned long long v);
int fmt_bool([]char buf, int v); // "true" / "false"
int fmt_str([]char dst, const &char src); // copy; returns chars copied
int fmt_append([]char dst, const &char src); // returns new length, -1 if cap exceeded
#include <std/fmt.h>
#include <std/io.h>
int main() {
char buf[32];
std::fmt_str(buf[..], "x = ");
char num[16];
std::fmt_int(num[..], 42LL);
std::fmt_append(buf[..], num);
std::println(buf); // x = 42
return 0;
}
Run in Playgroundheap — Global Heap
#include <std/heap.h>
One heap API for both environments. Hosted builds wrap malloc/free/realloc. Freestanding builds manage a static HEAP_SIZE-byte buffer (default 1 MiB, override with -DHEAP_SIZE=<bytes>) with TLSF, so every operation is O(1).
void heap_init(); // call once first (no-op when hosted)
&heap unsigned char heap_alloc(unsigned long size); // null on failure
void heap_free(&heap unsigned char ptr);
&heap unsigned char heap_realloc(&heap unsigned char ptr, unsigned long new_size); // null on failure; ptr stays valid
unsigned long heap_capacity(); // bytes; 0 when hosted (unbounded)
locale
#include <std/locale.h>
// name: "" = system default, "C", "en_US.UTF-8", ... Returns the new locale name, or null.
const ?&char locale_set(int category, const &char name);
const ?&char locale_get(int category);
Categories: LC_ALL(), LC_COLLATE(), LC_CTYPE(), LC_MONETARY(), LC_NUMERIC(), LC_TIME().
log — Logging
#include <std/log.h>
#define LOG_LEVEL_NONE 0
#define LOG_LEVEL_ERROR 1
#define LOG_LEVEL_WARN 2
#define LOG_LEVEL_INFO 3
#define LOG_LEVEL_DEBUG 4
// LOG_LEVEL defaults to LOG_LEVEL_INFO; set it with -DLOG_LEVEL=<n>.
typedef fn void(int level, const &char tag, const &char msg,
const &char file, int line) LogBackend;
void log_set_backend(LogBackend backend);
void log_write(int level, const &char tag, const &char msg, const &char file, int line);
log_set_backend routes every message to your function (a UART, a ring buffer, a file).
The LOG_E/LOG_W/LOG_I/LOG_D(tag, msg) macros fill in __FILE__/__LINE__ and compile to nothing below LOG_LEVEL, so LOG_LEVEL=0 costs nothing. They are function-like macros, so they exist only in compat preprocessor mode; in safe mode call log_write directly.
panic — Panics and expect_ref
#include <std/panic.h>
typedef fn void(const &char msg, const &char file, int line) PanicHandler;
void panic_set_handler(PanicHandler handler); // null restores the default
void panic_at(const &char msg, const &char file, int line); // never returns
// r as &T, or panic with msg if it is null.
generic<T> &T expect_ref(?&T r, const &char msg);
The default handler loops forever on freestanding targets and calls abort() on hosted ones. expect_ref is the checked way to say "this can't be null here"; a cast (&T)r is rejected because it would skip the check. For absence you can recover from, use match.
PANIC(msg) and PANIC_ASSERT(cond) are available in compat preprocessor mode.
result — Result<T, E>
#include <std/result.h>
A value or an error, stored inline (no heap allocation).
generic<T, E>
struct Result {
T value; // valid when is_ok == 1
E error; // valid when is_ok == 0
int is_ok;
static struct Result<T, E> new_ok(T val); // Result::new_ok(v)
static struct Result<T, E> new_err(E err); // Result::new_err(e)
int ok() const;
int err() const;
?&T get_ok(); // null for an error
?&E get_err(); // null for a success
T unwrap_or(T fallback) consume; // the value, or 'fallback' (the other one is dropped)
T unwrap() consume; // the value; panics for an error result
E unwrap_err() consume; // the error; panics for an ok result
void drop(); // when T or E owns something (String, Vec, ...)
};
The variable's type supplies the type parameter the argument doesn't:
#include <std/result.h>
int main() {
struct Result<int, long long> r = std::Result::new_ok(42);
int v = r.get_ok().default(0); // 42
struct Result<int, long long> e = std::Result::new_err(7LL);
long long code = e.get_err().default(0LL); // 7
int fallback = e.unwrap_or(-1); // -1
return (v == 42 && code == 7LL && fallback == -1) ? 0 : 1;
}
Run in Playgroundsignal — POSIX Signals
#include <std/signal.h>
int signal_handle(int sig, fn void(int) handler); // 0, or -1 for an invalid or uncatchable signal
void signal_default(int sig);
void signal_ignore(int sig);
int signal_raise(int sig); // to this process
int signal_kill(int pid, int sig); // to another process
void signal_pause(); // block until a signal arrives
Signal numbers (POSIX values): SIG_HUP(), SIG_INT(), SIG_QUIT(), SIG_ILL(), SIG_ABRT(), SIG_FPE(), SIG_KILL(), SIG_SEGV(), SIG_PIPE(), SIG_ALRM(), SIG_TERM(), SIG_USR1(), SIG_USR2(), SIG_CHLD(), SIG_CONT(), SIG_STOP(), SIG_TSTP(), SIG_WINCH(). SIG_KILL and SIG_STOP can't be caught. SIG_USR1, SIG_USR2, SIG_CHLD, SIG_CONT, SIG_STOP and SIG_TSTP have different numbers on Linux and on macOS/BSD; the functions return the right one for the target.
signal_handle returns 0 on success and -1 for a signal that can't be caught; it does not hand back the previous handler (that may be one of the C library's SIG_DFL/SIG_IGN markers rather than a SafeC function). Use signal_default / signal_ignore to restore a disposition.
stdckdint — Checked Arithmetic
#include <std/stdckdint.h>
C23's ckd_add/ckd_sub/ckd_mul, one function per type. Each stores the wrapped result in *result and returns CKD_OVERFLOW (1) if the true result didn't fit, CKD_OK (0) otherwise.
int ckd_add_i32(&int result, int a, int b);
int ckd_sub_i32(&int result, int a, int b);
int ckd_mul_i32(&int result, int a, int b);
int ckd_add_i64(&long long result, long long a, long long b);
int ckd_sub_i64(&long long result, long long a, long long b);
int ckd_mul_i64(&long long result, long long a, long long b);
int ckd_add_u32(&unsigned int result, unsigned int a, unsigned int b);
int ckd_sub_u32(&unsigned int result, unsigned int a, unsigned int b);
int ckd_mul_u32(&unsigned int result, unsigned int a, unsigned int b);
int ckd_add_u64(&unsigned long long result, unsigned long long a, unsigned long long b);
int ckd_sub_u64(&unsigned long long result, unsigned long long a, unsigned long long b);
int ckd_mul_u64(&unsigned long long result, unsigned long long a, unsigned long long b);
The language also has overflow operators built in; see Overflow Operators.
sys — Process, Environment, Sorting, Clocks
#include <std/sys.h>
#define EXIT_SUCCESS 0
#define EXIT_FAILURE 1
#define RAND_MAX 2147483647
void sys_exit(int code);
void sys_abort();
int sys_system(const &char cmd);
int sys_atexit(fn void() func);
const ?&char sys_getenv(const &char name); // null if unset
int sys_rand(); // 0 .. RAND_MAX
void sys_srand(unsigned int seed);
int sys_abs(int x);
long long sys_llabs(long long x);
int sys_atoi(const &char s);
long long sys_atoll(const &char s);
double sys_atof(const &char s);
// base 0 auto-detects a 0x / 0 prefix. If end is non-null it receives the first unparsed character.
long long sys_strtoll(const &char s, ?&&const char end, int base);
unsigned long long sys_strtoull(const &char s, ?&&const char end, int base);
double sys_strtod(const &char s, ?&&const char end);
float sys_strtof(const &char s, ?&&const char end);
// Typed sort/search over a slice; cmp returns <0 / 0 / >0.
generic<T> void sys_qsort([]T items, fn int(const &T, const &T) cmp);
generic<T> ?unsigned long sys_bsearch(const &T key, []const T items,
fn int(const &T, const &T) cmp); // the index of a match, or null
long long sys_cpu_time_ns();
long long sys_time_now_ns();
long long sys_monotonic_ns();
end is an optional out-parameter: pass the address of a const &char to learn where parsing stopped, or null. For parsing with a success flag, prefer convert.h.
#include <std/sys.h>
static int cmp_int(const &int a, const &int b) { return *a - *b; }
int main() {
int xs[5] = { 5, 3, 9, 1, 7 };
std::sys_qsort(xs[..], cmp_int); // 1 3 5 7 9
int key = 7;
match (std::sys_bsearch(&key, xs[..], cmp_int)) {
case some(i): { return xs[i] == 7 ? 0 : 1; } // i is the index found
case none: { return 1; }
}
}
Run in Playgroundtime — Calendar and Clocks
#include <std/time.h>
Broken-down times are struct TimeFields, C's struct tm fields by name: sec, min, hour, mday (day of month), mon (0–11), year (years since 1900), wday (0 = Sunday), yday (day of year), isdst.
long long time_now(); // Unix seconds
struct TimeFields { int sec; int min; int hour; int mday; int mon; int year; int wday; int yday; int isdst; };
struct TimeFields time_gmtime(long long t); // UTC
struct TimeFields time_localtime(long long t);
long long time_mktime(const &TimeFields tm); // local broken-down -> Unix seconds, -1 on error
// strftime / strptime formats. time_format returns bytes written (0 on failure); time_parse returns 1 on success.
unsigned long time_format([]char buf, const &char fmt, const &TimeFields tm);
int time_parse(const &char s, const &char fmt, &TimeFields tm);
double time_diff(long long end, long long start); // seconds
long long time_wall_ns(); // CLOCK_REALTIME
long long time_mono_ns(); // CLOCK_MONOTONIC — use for elapsed time
long long time_cpu_ns(); // process CPU time
long long time_elapsed_ns(long long start, long long end);
double time_ns_to_ms(long long ns);
void time_sleep_ns(long long ns);
int time_days_in_month(int month, int year); // month 0-11, full year (e.g. 2024)
int time_is_leap(int year);
#include <std/time.h>
#include <std/io.h>
int main() {
struct TimeFields tm = std::time_gmtime(std::time_now());
char buf[32];
std::time_format(buf[..], "%Y-%m-%d %H:%M:%S", &tm);
std::println(buf);
long long t0 = std::time_mono_ns();
std::time_sleep_ns(1000000LL); // 1 ms
double ms = std::time_ns_to_ms(std::time_elapsed_ns(t0, std::time_mono_ns()));
return ms >= 1.0 ? 0 : 1;
}
dma — DMA Buffers and Channels
#include <std/dma.h>
Cache-coherent buffer ownership and a driver-agnostic DMA channel. Freestanding-safe; works with any allocator or static storage.
#define DMA_ALIGN 64 // typical burst alignment (one cache line)
struct DmaBuffer {
&unsigned char virt; // CPU view of the buffer
unsigned long phys; // device address (0 = identity-mapped)
unsigned long size;
int owner; // 0 = CPU, 1 = device
void to_device(); // flush CPU cache, hand to device
void to_cpu(); // invalidate cache after the transfer, hand back
int cpu_accessible() const;
unsigned long phys_at(unsigned long offset) const;
// Wraps an existing allocation; phys 0 = identity-mapped.
static struct DmaBuffer wrap([]unsigned char virt, unsigned long phys);
};
// One driver owns a channel, so its callback context is typed.
generic<T> struct DmaChannel {
unsigned int id;
int busy;
fn int(?&T, unsigned long, unsigned long, unsigned long) start_fn; // (ctx, src, dst, len) -> 0 on success
fn int(?&T) poll_fn; // (ctx) -> 1 when complete
int has_poll; // 0: wait() returns at once (synchronous engines)
?&T ctx;
int start(unsigned long src_phys, unsigned long dst_phys, unsigned long len); // -1 if busy
int wait(unsigned int max_polls); // 0 when idle, -1 on timeout
int is_busy() const;
// T is inferred from the callbacks: DmaChannel::new(1U, start, poll, ctx)
static struct DmaChannel<T> new(unsigned int id,
fn int(?&T, unsigned long, unsigned long, unsigned long) start_fn,
fn int(?&T) poll_fn, ?&T ctx);
};
// Scatter-gather list, up to DMA_SG_MAX (16) entries.
struct DmaSgEntry { unsigned long phys; unsigned long len; };
struct DmaSgList {
struct DmaSgEntry entries[DMA_SG_MAX];
int count;
void add(unsigned long phys, unsigned long len);
void clear();
unsigned long total_len() const;
};
virt refers to the first byte of the buffer, which can live in any region (a static DMA pool, the heap, an arena).
assert — Runtime Assertions
#include <std/assert.h>
// On failure: prints "Assertion failed: <msg> at <file>:<line>" to stderr, then abort().
void runtime_assert_at(int cond, const &char msg, const &char file, int line);
// The same without a location: a function can't see its call site.
void runtime_assert(int cond, const &char msg);
void assert_true(int cond); // no message
In compat preprocessor mode, runtime_assert and assert_true are macros that pass the caller's __FILE__ and __LINE__ to runtime_assert_at, and defining NDEBUG before the include turns them into no-ops. In safe mode, call runtime_assert_at(cond, msg, __FILE__, __LINE__) for a location. static_assert is a language keyword and is always checked at compile time.
C Type and Limit Headers
These headers only define types and constants, matching their C11/C23 counterparts.
| Header | Provides |
|---|---|
stdint.h |
int8_t…int64_t, uint8_t…uint64_t, intptr_t/uintptr_t, intmax_t/uintmax_t; INT8_MIN/INT8_MAX … INT64_MIN/INT64_MAX, UINT8_MAX … UINT64_MAX, INTPTR_MIN/INTPTR_MAX/UINTPTR_MAX, PTRDIFF_MIN/PTRDIFF_MAX, SIZE_MAX, INTMAX_MIN/INTMAX_MAX/UINTMAX_MAX |
stddef.h |
size_t, ssize_t, ptrdiff_t, max_align_t, NULL, offsetof — pointer-sized types are 32-bit on 32-bit targets (e.g. wasm32) and 64-bit elsewhere |
stdbool.h |
bool, true, false |
limits.h |
CHAR_BIT, SCHAR_MIN/SCHAR_MAX, UCHAR_MAX, CHAR_MIN/CHAR_MAX, SHRT_MIN/SHRT_MAX, USHRT_MAX, INT_MIN/INT_MAX, UINT_MAX, LONG_MIN/LONG_MAX, ULONG_MAX, LLONG_MIN/LLONG_MAX, ULLONG_MAX, MB_LEN_MAX |
float.h |
FLT_/DBL_/LDBL_ × MANT_DIG, DIG, MIN_EXP, MAX_EXP, EPSILON, MIN, MAX (plus FLT_/DBL_ MIN_10_EXP, MAX_10_EXP, TRUE_MIN); FLT_RADIX, FLT_ROUNDS, FLT_EVAL_METHOD, DECIMAL_DIG |
inttypes.h |
printf/scanf format strings: PRId8…PRId64, PRIu*, PRIo*, PRIx*, PRIX*, plus MAX/PTR variants and the matching SCN* macros |