BetterC Guide — GC-free D with Mir
How to use Mir libraries in D's -betterC mode — no runtime, no GC, no exceptions. Covers mir-core, mir-algorithm, and mir-random in betterC contexts with practical examples.
D's -betterC flag disables the D runtime and garbage collector, making the resulting code directly linkable with C programs, embedded systems, and WebAssembly targets. Mir was designed with this use case in mind — its core libraries work fully in -betterC.
BetterC was one of the original motivations for the Mir project. The 2016 D forum thread "A betterC modular standard library" describes the design intent behind libmir.org.
What betterC disables
When you compile with -betterC (or "dflags": ["-betterC"] in dub.json):
- No GC —
new Tis forbidden unless you manage memory manually - No D runtime — no
TypeInfo, no module constructors - No exceptions —
throwandtry/catchare unavailable - No
assertwith messages — useassert(cond)only, no string arg
What still works: @safe, @nogc, nothrow, pure, templates, CTFE, static foreach, unions, structs, function pointers.
Setting up a betterC project
// dub.sdl
name "myproject"
targetType "staticLibrary"
dependency "mir-algorithm" version="~>3.0"
dependency "mir-core" version="~>1.0"
dflags "-betterC"To detect BetterC at compile time within source code (does not enable it — use the compiler flag or dflags for that):
// betterc_module.d
module mylib.compute;
version (D_BetterC) {
// Code in this block compiles only when -betterC is active
}Using ndslice in betterC
slice!T(...) uses the GC — avoid it. Use rcslice (reference-counted) or wrap external buffers:
import mir.ndslice;
import mir.rc.array : rcslice;
// Option 1: Reference-counted (no GC)
auto m = rcslice!double(3, 4);
m[] = 0.0;
// Option 2: Wrap C-allocated memory
import core.stdc.stdlib : malloc, free;
auto ptr = cast(double*) malloc(12 * double.sizeof);
scope(exit) free(ptr);
auto m2 = ptr.sliced(3, 4); // no ownership, no allocationAlgebraic types in betterC
mir.algebraic works fully without the runtime:
import mir.algebraic;
alias Result = Variant!(int, string);
extern(C) int compute(int x) @nogc nothrow
{
Result r = x > 0 ? Result(x * 2) : Result("negative");
return r.match!(
(int v) => v,
(string s) => -1,
);
}Random numbers in betterC
import mir.random;
import mir.random.variable : NormalVariable;
extern(C) double sampleNormal() @nogc nothrow
{
static Random rng;
static bool init = false;
if (!init) { rng = Random(42); init = true; }
auto n = NormalVariable!double(0.0, 1.0);
return n(rng);
}Error handling without exceptions
Without exceptions, use mir.algebraic result types:
import mir.algebraic;
struct ParseError { string message; }
alias ParseResult = Variant!(double, ParseError);
ParseResult parseDouble(const(char)[] s) @nogc nothrow
{
import mir.parse : fromString;
double val;
if (!s.fromString(val))
return ParseResult(ParseError("invalid number"));
return ParseResult(val);
}
// At the call site:
auto r = parseDouble("3.14");
r.match!(
(double v) => writeln("parsed: ", v),
(ParseError e) => writeln("error: ", e.message),
);Interop with C
BetterC D compiles to standard object files that link directly with C:
// mylib.d — compiled with -betterC
import mir.ndslice;
extern(C) void normalize(double* data, size_t n) @nogc nothrow
{
auto v = data.sliced(n);
import mir.algorithm.iteration : reduce;
import mir.math.common : sqrt;
double sum2 = v.reduce!"a + b*b"(0.0);
double norm = sqrt(sum2);
if (norm > 0)
v[] = v.map!(x => x / norm);
}// main.c
extern void normalize(double* data, size_t n);
int main() {
double data[] = {1.0, 2.0, 3.0};
normalize(data, 3);
// data is now [0.267, 0.535, 0.802]
}Compile:
ldc2 -betterC -c mylib.d -of=mylib.o
cc main.c mylib.o -o appWhat to avoid
| Pattern | Problem | Alternative |
|---|---|---|
new T(...) | GC allocation | Stack alloc, rcslice, or manual malloc |
throw e | Exceptions | Return Variant!(T, Error) |
string literals in assert | Runtime | assert(cond) only |
foreach (k, v; aa) | AA uses GC | Use sorted arrays or hash maps from mir |
std.conv.to!string | GC | mir.format or mir.conv |
writeln | Runtime | printf or mir.stdio |
WebAssembly target
BetterC D compiles to Wasm via LDC:
ldc2 -betterC -mtriple=wasm32-unknown-unknown-wasm \
-link-internally -of=mylib.wasm mylib.dMir's @nogc guarantee means zero GC pauses — critical for Wasm performance.
Linear Algebra in D — Matrix Operations with mir-blas
Matrix-vector and matrix-matrix products, dot products, and BLAS operations on ndslice in D with mir-blas. Compiled against current Mir source.
About this archive
Why libmir.org went down, who maintains this archive, how content is sourced, and how to contribute.