C v0.1.0

Source
C standard library bindings

Installation

Add C to the [Dependencies] table of your package's Rux.toml:

[Dependencies]
C = { Namespace = "Rux", Version = "0.1.0" }

Standard packages are declared by hand: the registry does not serve them, so rux add Rux/C cannot add them.

Dependencies

Guide

C standard library bindings, for interoperating with existing native code.

Nothing else in the standard packages depends on this one. Rux/Io, Rux/Memory, and Rux/Math each reach the platform directly instead, so a Rux program links no C runtime unless it asks for one by importing this package.

What it provides

ModuleCovers
Typesthe C ABI's own types, errno access, and the opaque handles
String<char8[..].h> — counted memory operations and terminated byte strings
StdIo<stdio.h> — streams, printf family, fopen/fclose, positioning
StdLib<stdlib.h> — allocation, conversion, process control, sorting, searching
Math<math.h> — the elementary functions in both double and float forms
Time<time.h> — clocks, calendar time, and timespec

A declaration that has to match a C interface is written against the Types aliases rather than Rux's own widths: c_int, c_long and c_ulong (which the two data models disagree about), c_char and its two explicit-signedness siblings, size_t, ssize_t, ptrdiff_t, intptr_t, uintptr_t and wchar_t. A stream is a *FILE and a stream position is a *fpos_t, both declared with no fields, so one handle cannot be passed where another was meant. ErrnoLocation returns the address of the calling thread's errno through whichever accessor the platform exports, Errno reads it, ErrnoLocationFor and ErrnoFor name which of the package's runtimes to read, and EDOM, ERANGE and EILSEQ are the three values C itself mandates.

Two C runtimes on Windows

StdLib and Math bind the Universal CRT (ucrtbase.dll); StdIo and Time bind the legacy msvcrt.dll, which is the only one exporting the formatted-output family. They are two loaded modules with two distinct _errno entry points, and CRuntime and StreamRuntime name them. SeparateStreamRuntime is true here and false everywhere else, where both constants name the one runtime.

ErrnoFor(CRuntimeArea::Streams) reads the runtime StdIo and Time are bound to and ErrnoFor(CRuntimeArea::General) the one StdLib and Math are bound to, so a caller reads the errno belonging to the call it actually made. Plain Errno is the General case. On the three Unix systems both areas name the same storage, so passing the area matching the module called is right on all four targets and needs no branch in caller code.

What was measured, on Windows 11 with the current system DLLs. The two _errno functions are different exports at different addresses, and both return the same per-thread storage: today's msvcrt.dll defers to the Universal CRT rather than keeping an errno of its own. Separately, a failing fopen through msvcrt.dll sets no errno at all, in either runtime — its failure is the null FILE* and nothing else. So the rule to follow after a StdIo or Time call is to read the return value, not errno; and where errno is consulted anyway, ErrnoFor is what names the runtime rather than leaving it to whichever accessor happened to be linked. A different Windows may resolve the two differently, which is exactly why the area is passed rather than assumed.

Both StdLib and StdIo are curated rather than complete, and what is left out is left out on purpose. gets is not declared at all: it reads a line into a buffer whose size it is never told, and C11 removed it. The ato* conversions are declared but each one says to prefer the strto* family beside it, which can report both where it stopped and a value out of range. sprintf and the scanf family are marked as writing as much as the format says to; tmpnam as naming a file it does not create; system as running whatever the shell decides its argument means; rand as guaranteeing nothing about its sequence and never to be used for anything an adversary would like to predict.

String holds two families that are not alike. The mem* calls take a length and touch exactly that many bytes; the str* calls take none and stop at the first zero byte, which means the caller has promised there is one. Where both forms exist the counted one is the one to reach for, and Rux/Memory and Rux/Text do these jobs with lengths that are checked. Each declaration records what it requires of its arguments, including the traps worth naming: strncpy does not terminate a source that fills the count, strcat walks the destination from its start on every call, and strtok keeps its position in storage shared by the whole program.

Each module selects the right declarations for the target at compile time, so the same import works across the supported platforms. rux check --target <triple> resolves every declaration here on all eight supported cells, and the Layout test checks the sizes and the runtime-filled structures on whichever one it runs.

Values and pointers

C ABI records such as timespec are ordinary structural values and copy by value. Safe package helpers borrow them: TimespecGet(value, TIME_UTC) takes a mutable reference, so callers pass the value itself. C declarations keep raw pointers where the ABI permits null, returns an address, accepts an untyped buffer, or relies on pointer arithmetic. Those calls inherit C's validity, lifetime, alignment, and overlap requirements; use Rux/Memory, Rux/Text, or Rux/Io when a checked interface exists.

Two things are absent from Math on purpose. fabsf, frexpf, ldexpf and hypotf are header inlines in the Universal CRT rather than exported symbols, so declaring them would link on Unix and fail on Windows. The nexttoward pair takes a long double, which is a different type on every target — 64 bits under the Universal CRT, an 80-bit x87 value on x86-64 Unix, a 128-bit quad on AArch64 — so no one declaration is right everywhere. Rux/Math computes at every width without a C runtime and is the portable answer.

The module names above describe where each declaration comes from; they are not part of an import path.

Example

Declarations are flat under the package, so an import names the function rather than the header it came from:

import C::{ CRuntimeArea, ErrnoFor, ErrnoLocationFor, free, malloc, puts };

func Main() -> int {
    // A Rux literal is a `char8[..]` and a C declaration takes a `*char8`, so the address is passed explicitly.
    var buffer = malloc(1024);
    if buffer == null {
        puts("allocation refused".data);
        return 1;
    }

    puts("allocated".data);
    free(buffer);

    // Clearing before the call is how C's `errno = 0` is spelled; nothing clears it on success. `malloc` comes from
    // `StdLib`, so its failure is readable in the `General` area.
    *ErrnoLocationFor(CRuntimeArea::General) = 0i32;
    let refused = malloc(18446744073709551615u64);
    if refused == null && ErrnoFor(CRuntimeArea::General) != 0i32 {
        puts("and the refusal reported a reason".data);
    }
    return 0;
}

This one compiles and runs on all eight target cells; the previous version of it did not compile at all, because it passed a literal where a *char8 was wanted.

Index

Types

NameSummary
time_tC's time_t: a calendar time as an arithmetic value.
clock_tC's clock_t: processor time as an arithmetic value.
tmC's tm: a calendar time broken into its components.
timespecThe public timespec data layout.
c_scharC's signed char: the smallest addressable signed integer.
c_ucharC's unsigned char, which is also the type a byte of arbitrary storage is read through.
c_charC's plain char, whose signedness the standard leaves to the implementation.
c_shortC's short.
c_ushortC's unsigned short.
c_intC's int, which is 32 bits on every currently supported target.
c_uintC's unsigned int.
c_longlongC's long long.
c_ulonglongC's unsigned long long.
c_floatC's float.
c_doubleC's double.
c_boolC's _Bool, which occupies one byte and holds zero or one.
c_longC's long, 32 bits under this target's LLP64 data model.
c_ulongC's unsigned long, 32 bits under this target's LLP64 data model.
wchar_tC's wchar_t, a 16-bit UTF-16 code unit on Windows rather than a whole scalar value.
size_tC's size_t: the type of a size, a count of bytes, and the result of sizeof.
ssize_tPOSIX's ssize_t: a size that can also report -1 for failure.
ptrdiff_tC's ptrdiff_t: the signed difference between two pointers into one object.
intptr_tC's intptr_t: a signed integer wide enough to hold any pointer.
uintptr_tC's uintptr_t: an unsigned integer wide enough to hold any pointer.
FILEC's FILE: a stream, whose contents belong to the C runtime.
fpos_tC's fpos_t: an opaque record of a position within a stream.
CRuntimeAreaWhich of the package's C runtimes a call belongs to.

Functions

NameSummary
cbrtComputes cube root.
ceilComputes smallest integer not less than the given value.
copysignCopies the sign of a floating-point value.
cosComputes cosine.
expComputes e raised to the given power.
fabsComputes the absolute value of a floating-point value.
fdimComputes positive difference of two floating-point values.
floorComputes largest integer not greater than the given value.
fmaxComputes larger of two floating-point values.
fminComputes smaller of two floating-point values.
fmodComputes remainder of the floating-point division operation.
hypotComputes square root of the sum of the squares of two given numbers.
ldexpMultiplies a number by 2 raised to an integer power.
logComputes natural (base e) logarithm.
log2Computes base 2 logarithm.
powComputes a number raised to the given power.
roundRounds to nearest integer, rounding away from zero in halfway cases.
scalblnComputes sine.
scalblnfMultiplies a floating-point value by an integral power of two, with a long exponent.
sinComputes sine.
sqrtfComputes square root.
sqrtComputes square root.
tanComputes tangent.
truncRounds to nearest integer not greater in magnitude than the given value.
fcloseCloses the given file stream.
fgetcReads the next character from the given stream.
fopenOpens a file indicated by filename with the given mode.
fseekSets the file position indicator for the stream.
ftellReturns the current file position of the stream.
fwriteWrites count objects of the given size from a buffer to the stream.
putcharWrites a character to stdout.
putsWrites a string followed by a newline to stdout.
removeDeletes the file identified by the given path.
sprintfPrints formatted output to a string.
sscanfReads formatted input from a string.
absComputes the absolute value of an integer value.
atofConverts a byte string to a floating point value.
atoiConverts a byte string to an integer value.
atolConverts a byte string to a long integer value.
atollConverts a byte string to a long long integer value.
callocAllocates memory for an array of num objects of size and zero-initializes it.
freeDeallocates the memory previously allocated by malloc, calloc or realloc.
labsComputes the absolute value of a long integer value.
llabsComputes the absolute value of a long long integer value.
mallocAllocates size bytes of uninitialized memory.
randReturns a pseudo-random integer value between 0 and RAND_MAX.
reallocChanges the size of the memory block pointed to by ptr to size bytes.
srandSeeds the pseudo-random number generator used by rand.
strtolConverts a byte string to a long integer, reporting where it stopped.
strtoulConverts a byte string to an unsigned long integer, reporting where it stopped.
strtollConverts a byte string to a long long integer, reporting where it stopped.
strtoullConverts a byte string to an unsigned long long integer, reporting where it stopped.
strtodConverts a byte string to a double, reporting where it stopped.
strtofConverts a byte string to a float, reporting where it stopped.
qsortSorts an array in place; the comparison must be a strict weak ordering and the sort is not stable.
bsearchFinds an element of a sorted array; the array must already be ordered by the same comparison.
memchrFinds the first byte equal to ch within the first count bytes.
memcmpCompares the first count bytes of two objects.
memcpyCopies count bytes from src to dest.
memmoveCopies count bytes from src to dest, correctly even when the two overlap.
memsetWrites ch to each of the first count bytes of dest.
strlenThe length of a byte string, not counting its terminator.
strcmpCompares two byte strings.
strncmpCompares at most count bytes of two byte strings.
strcpyCopies a byte string, terminator included.
strncpyCopies at most count bytes of a byte string, padding with zeros.
strcatAppends a byte string to the end of another.
strncatAppends at most count bytes of a byte string to the end of another.
strchrFinds the first occurrence of a byte in a byte string.
strrchrFinds the last occurrence of a byte in a byte string.
strstrFinds the first occurrence of one byte string within another.
strspnThe length of the leading run of bytes drawn only from accept.
strcspnThe length of the leading run of bytes drawn from none of reject.
strpbrkFinds the first byte of str that occurs in accept.
strtokSplits a byte string into tokens separated by any byte of delimiters.
clockReturns the approximate processor time used by the program.
difftimeComputes the difference in seconds between two calendar times.
gmtimeConverts a time_t object to calendar time expressed as UTC.
localtimeConverts a time_t object to calendar time expressed as local time.
mktimeConverts calendar time to a time_t object, normalizing the tm structure.
timeReturns the current calendar time since the epoch.
TimespecGetFills ts with the current time on the given base.
ErrnoLocationForThe address of the calling thread's errno, in the runtime area names.
ErrnoForThe calling thread's current errno value, in the runtime area names.
ErrnoLocationThe address of the calling thread's errno, in the runtime C::StdLib and C::Math are bound to.
ErrnoThe calling thread's current errno value.

Constants

NameSummary
HUGE_VALHUGE_VAL: what a double function returns when the true result is too large to express.
HUGE_VALFHUGE_VALF: the float form of HUGE_VAL.
INFINITYINFINITY: a positive infinity, for building a value rather than for testing one.
NANNAN: a quiet not-a-number, for building a value rather than for testing one.
EOFEOF: what a character-reading call returns instead of a character when the stream has ended or failed.
SEEK_SETSEEK_SET: measure the new stream position from the beginning.
SEEK_CURSEEK_CUR: measure the new stream position from the current one.
SEEK_ENDSEEK_END: measure the new stream position from the end.
_IOFBF_IOFBF: buffer fully, flushing when the buffer fills.
_IOLBF_IOLBF: buffer by line.
_IONBF_IONBF: do not buffer.
BUFSIZBUFSIZ: the buffer size setbuf uses.
FOPEN_MAXFOPEN_MAX: how many streams the implementation guarantees can be open at once.
FILENAME_MAXFILENAME_MAX: the length of the longest file name the implementation guarantees it can open.
L_tmpnamL_tmpnam: the buffer size tmpnam needs.
TMP_MAXTMP_MAX: how many distinct names tmpnam guarantees before it repeats.
EXIT_SUCCESSEXIT_SUCCESS: the status a program returns to mean it did what it was asked.
EXIT_FAILUREEXIT_FAILURE: the status a program returns to mean it did not.
RAND_MAXRAND_MAX: the largest value rand can return on this target.
TIME_UTCTIME_UTC: the time base naming seconds since the epoch, the only one C defines.
CLOCKS_PER_SECCLOCKS_PER_SEC: how many clock ticks make one second on this target.
CRuntimeThe shared object this target's C runtime lives in.
StreamRuntimeThe shared object C::StdIo and C::Time are bound to, which on Windows is a different one.
SeparateStreamRuntimeWhether the two areas are really two runtimes on this target.
EDOMEDOM: an argument outside the domain over which a math function is defined.
ERANGEERANGE: a result too large or too small for the return type to express.
EILSEQEILSEQ: a byte sequence that does not form a valid character.