MULE 0.0.1
Multi-Use Library Essentials
Loading...
Searching...
No Matches
How MULE works

Each module's topic page explains that module. This page explains the rules every module shares. Learn these once and each function behaves the way you expect before you read its documentation.

Using the library

A project copies one file, dist/mule.h. Include it anywhere for the declarations. In exactly one .c file, define MULE_IMPLEMENTATION first, and the library compiles into that file:

#define MULE_IMPLEMENTATION
#include "mule.h"

Include it before any system header. Base sets _POSIX_C_SOURCE, which only takes effect before the first one. Define MULE_TEST as well to get the Test harness.

Inside this repository, programs build as one translation unit instead. Each test and example includes the .c files it needs, as in #include "str/str.c". That is why every example here compiles with a single compiler call.

The library needs a C11 compiler and is written in the style of C99. It is tested with clang 17+ and gcc 13+ on macOS and Linux.

Modules

Each module is a header and a source file under src/. They depend on each other in this order, so a module only uses the ones above it.

Module Gives you Uses
Base Feature macros, bool, checked arithmetic
Mem Allocator, Arena base
Str and StrView Str, StrView mem
Arr Arr(T) mem
Rand Rand base
Time time_now_ns() base
Log log_info() and friends, log_assert() str
Test TEST, CHECK, RUN log

Failure is a bool

A function that can fail returns bool. False means it did nothing: a failed call leaves every argument byte for byte as it was. The tests prove this by making each allocation fail in turn and comparing the arguments afterwards.

Because nothing changes on failure, calls chain with &&, and the chain stops at the first one that fails:

bool ok = str_append_cstr(&s, "Rodion") &&
str_push(&s, ' ') &&
str_appendf(&s, "age %d", 23);
bool str_appendf(Str *s, const char *fmt,...)
Append the printf-style expansion of fmt.
Definition str.c:123
bool str_push(Str *s, char c)
Append the single byte c.
Definition str.c:76
bool str_append_cstr(Str *s, const char *cstr)
Append cstr.
Definition str.c:119

The compiler warns when a result that must be checked is ignored. gcc warns even through a (void) cast.

Two other kinds of failure do not return bool:

  • Programming errors are caught by assert, such as slicing past the end of a view with sv_slice(). Each function lists them under Precondition. They vanish from release builds.
  • Unrecoverable states go through log_assert() and log_fatal(), which print where they happened and abort, in release builds too.

Zero is a valid value

Str, StrView, Arena, Arr and Rand are ready to use when zero-initialised, and own nothing until something is put in them:

Str s = { 0 };
Arena arena = { 0 };
IntArr xs = { 0 };
Rand r = { 0 };
Bump allocator over a chain of blocks.
Definition mem.h:86
Generator state.
Definition rand.h:26
Owned, growable byte string.
Definition str.h:64

The free functions return a value to this empty state, keeping only its allocator. Calling one twice is safe, and a struct whose fields were zero-initialised can be freed even when it was only half filled in.

Memory

Who allocates

Every allocation goes through an Allocator. A Str or an Arr holds a pointer to one in its alloc field, and null means the libc heap. Point it at an arena with arena_allocator(), and the value grows inside the arena and is freed with it:

Arena arena = { 0 };
Str s = { .alloc = arena_allocator(&arena) };
CHECK(str_append_cstr(&s, "lives in the arena"));
CHECK(str_appendf(&s, ", %d bytes", 18));
CHECK_EQ_STR(str_cstr(&s), "lives in the arena, 18 bytes");
arena_free(&arena); /* frees the string's bytes too */

Who frees

The value that owns memory frees it. A Str is freed with str_free(), an Arr with arr_free(), and an arena with everything in it by arena_free(). A struct that holds owning fields gets its own _free function that frees each one.

Never copy an owning struct by assignment. Str b = a; copies the pointer, not the bytes, and freeing both is a double free. Copy the contents with str_from(), or move them by copying the struct and resetting the source to { 0 }.

How long a pointer lives

Anything that points into a buffer dies when the buffer moves:

Reserve the full size first with str_reserve() or arr_reserve(), and the appends that fit will not move the buffer.

Naming

  • Every public function starts with its module's prefix, such as str_, sv_, arena_ and log_. Types are CamelCase, and macros that do not look like functions are UPPER_CASE.
  • Some operations are macros that look like functions, such as arr_push() and log_info(). They are macros because no function could do the job: work for any element type, or capture the caller's file and line. The reference marks each one as a macro.
  • Names ending in _ are internal and may change. They are left out of this reference.

Threads

The library keeps no global state except the log level and the log output. Set both before starting threads. Each log line is written with one fwrite, so lines from different threads do not interleave. Every other value belongs to whoever holds it, so give each thread its own, or lock around a shared one.

Versions

Releases follow Semantic Versioning. Each one is a GitHub release with mule.h attached, and CHANGELOG.md lists what changed. Before 1.0.0, a minor release may break the API, and a patch release never does. MULE_VERSION and the MULE_VERSION_* macros in Base say which version a program was built with.

This reference follows the main branch, which may be ahead of the latest release.

Where to go next

  • The Str and StrView tutorial teaches the two string types with programs you can run.
  • Examples lists complete programs. Each one links every library call to its documentation.
  • Each function's documentation links to its definition, so you can read how it works, comments included.
  • The tests in tests/ exercise every function, including its failure paths, and most examples in this reference are cut from them.