Axle v0.14.1
Package

std/ffi/libc/cstr

libc <string.h> functions taking an Axle string (marshalled as a
NUL-terminated i8*) rather than a raw ptr. Declared once here and
shared across the stdlib by use std::ffi::libc::cstr::…, so the
@link(symbol = …) bindings live in a single place — the same pattern
fs.axle / mem.axle already follow.

These sit beside mem's ptr-typed libc_strlen / libc_memmove:
both forms bind the same libc symbol, which the compiler declares once
however many bindings a program imports. A module picks the form matching
its operands — the string-typed bindings here for pure-Axle text code,
the ptr-typed ones in mem for raw-buffer code.

Reference: https://en.cppreference.com/w/c/string/byte

Free functions

TypeMethod and description
i64
strlen(s : string) : i64 Byte length of a string — an Axle string carries no interior NUL,
so this is its exact byte count. This is the libc strlen (an O(N) NUL
scan); prefer strByteLen below, which reads the length header in O(1).
i64
strByteLen(s : string) : i64 Byte length of a string in O(1) — reads the i64 length header
every Axle string carries, not a NUL scan. Binds to the runtime ABI
symbol axle_string_byte_len (the runtime owns the string layout, like
axle_string_free); declared here in a low module so every text module
reaches it without a dependency cycle. Prefer this over strlen.

Null-safe (returns 0 for a null pointer) — the property that costs an
opaque call LLVM cannot see through the ABI boundary of. The
HashMap<string, V> probe's compare side (__stringEquals /
__stringEqualsIgnoreCase / __stringCompare / __stringHashCode in
std/text/text) instead reads the same header through the
__string_byte_len compiler builtin — no FFI call, and textually
identical to the read hashStringSeeded (std/text/hashing) already
emits for the same key, so LLVM's CSE collapses the two per probed
slot. That builtin skips the null guard, which is sound only where the
operand is proven non-nullable string; everywhere else (an operand
that might be a raw/foreign pointer) stays on strByteLen.
i32
strByteAt(s : string, i : i64) : i32 Bounds-checked byte read of s at byte-index i — -1 when i is
negative or at/beyond s's byte length. Binds to the runtime ABI
symbol axle_string_byte_at (the runtime owns the string layout, like
axle_string_byte_len); backs String.byteAt, the primitive every
hand-rolled parser (CSV / JSON / base64) scans through.
i32
strCharCountPrefix(s : string, byteEnd : i64) : i32 UTF-8 character count of the byte prefix [0, byteEnd) of s — every
byte that is not a continuation byte (0b10xxxxxx) starts a new
character; byteEnd is clamped to s's byte length. Binds to the
runtime ABI symbol axle_string_char_count_prefix (the runtime owns
the string layout, like axle_string_byte_len); backs String.length
/ indexOf / substring's character-offset maths.
ptr
copyFromString(dst : ptr, src : string, n : i64) : ptr Bulk-copy n bytes of a string's payload to dst, which may not
overlap the source. The read side of materialising a string into a
scratch buffer.

The non-overlap is a property of the string representation, not a
promise asked of the caller: a string's payload is either a static
literal global or its own heap allocation. stringFromBytes — the one
bridge from raw bytes to a string — performs one allocation and one
copy, so a string is never a view into a scratch buffer, and no
dst a caller can name is reachable from src. Fabricating a
borrowing string would take an unsafe pointer cast the surface does
not offer.

That is what earns memcpy over memmove: the overlap test is dead
work, and glibc's memcpy reaches its ERMS paths where memmove does
not.

Method detail

#strlen

strlen(s : string) : i64

Byte length of a string — an Axle string carries no interior NUL,
so this is its exact byte count. This is the libc strlen (an O(N) NUL
scan); prefer strByteLen below, which reads the length header in O(1).

#strByteLen

strByteLen(s : string) : i64

Byte length of a string in O(1) — reads the i64 length header
every Axle string carries, not a NUL scan. Binds to the runtime ABI
symbol axle_string_byte_len (the runtime owns the string layout, like
axle_string_free); declared here in a low module so every text module
reaches it without a dependency cycle. Prefer this over strlen.

Null-safe (returns 0 for a null pointer) — the property that costs an
opaque call LLVM cannot see through the ABI boundary of. The
HashMap<string, V> probe's compare side (__stringEquals /
__stringEqualsIgnoreCase / __stringCompare / __stringHashCode in
std/text/text) instead reads the same header through the
__string_byte_len compiler builtin — no FFI call, and textually
identical to the read hashStringSeeded (std/text/hashing) already
emits for the same key, so LLVM's CSE collapses the two per probed
slot. That builtin skips the null guard, which is sound only where the
operand is proven non-nullable string; everywhere else (an operand
that might be a raw/foreign pointer) stays on strByteLen.

Parameters
s string whose byte length is read

#strByteAt

strByteAt(s : string, i : i64) : i32

Bounds-checked byte read of s at byte-index i — -1 when i is
negative or at/beyond s's byte length. Binds to the runtime ABI
symbol axle_string_byte_at (the runtime owns the string layout, like
axle_string_byte_len); backs String.byteAt, the primitive every
hand-rolled parser (CSV / JSON / base64) scans through.

Parameters
s string to read
i byte offset into s, 0-based

#strCharCountPrefix

strCharCountPrefix(s : string, byteEnd : i64) : i32

UTF-8 character count of the byte prefix [0, byteEnd) of s — every
byte that is not a continuation byte (0b10xxxxxx) starts a new
character; byteEnd is clamped to s's byte length. Binds to the
runtime ABI symbol axle_string_char_count_prefix (the runtime owns
the string layout, like axle_string_byte_len); backs String.length
/ indexOf / substring's character-offset maths.

Parameters
s source string
byteEnd exclusive end byte offset of the prefix to count

#copyFromString

copyFromString(dst : ptr, src : string, n : i64) : ptr

Bulk-copy n bytes of a string's payload to dst, which may not
overlap the source. The read side of materialising a string into a
scratch buffer.

The non-overlap is a property of the string representation, not a
promise asked of the caller: a string's payload is either a static
literal global or its own heap allocation. stringFromBytes — the one
bridge from raw bytes to a string — performs one allocation and one
copy, so a string is never a view into a scratch buffer, and no
dst a caller can name is reachable from src. Fabricating a
borrowing string would take an unsafe pointer cast the surface does
not offer.

That is what earns memcpy over memmove: the overlap test is dead
work, and glibc's memcpy reaches its ERMS paths where memmove does
not.