The SafeC programming language

Safe, deterministic systems programming

SafeC is C, evolved: compile-time memory safety through regions, no garbage collector and no hidden runtime, while keeping C's ABI, its headers and its toolchain. Write kernels, audio engines, servers, GUIs and WebAssembly in one language you can read like C.

Open source (MIT) · Linux, macOS, Windows · bare metal and WebAssembly · LLVM backend

Latest release: SafeC 1.0.0-beta-7 — web stack, async, GUI, audio, slices and compiler-checked ownership

c
extern int printf(const char* fmt, ...);

region Frame { capacity: 4096 }

struct Particle { float x; float v; };

// The region is part of the type: p can't outlive the arena.
void step(&arena<Frame> Particle p, float dt) {
    p.x = p.x + p.v * dt;
}

int main() {
    &arena<Frame> Particle p = new<Frame> Particle;
    p.x = 0.0;
    p.v = 2.5;
    for (int i = 0; i < 3; i = i + 1) {
        step(p, 0.5);
        printf("t=%d x=%.2f\n", i, p.x);
    }
    arena_reset<Frame>();   // O(1): no per-object free, no GC
    return 0;
}
Run in Playground

Why SafeC

Keep everything that makes C the language of systems — predictable performance, a stable ABI, every platform — and let the compiler reject the bugs C lets through.

Memory safety without a GC

Every reference carries its region — stack, heap, arena<R> or static — and the compiler proves it can't dangle, alias mutably or escape. No collector, no reference counts, no runtime metadata.

Speaks C natively

C struct layout, C calling conventions and C symbol names by default. #include <stdio.h> works; link SafeC objects into any C or C++ project and call C libraries without bindings.

Deterministic by design

No implicit allocations, no exceptions, no hidden copies, no narrowing conversions. The same input gives the same output with costs you can see — what real-time audio and control loops need.

Bare metal and WebAssembly

--freestanding builds for 15 no-OS targets (Cortex-M, RISC-V, x86, AArch64) with interrupt handlers, inline assembly and volatile MMIO. --target=wasm32 builds browser modules with std/wasm DOM bindings.

Async without a runtime tax

std/async brings Rust-style futures and wakers as a library: stackless state machines, a no-heap single-threaded executor that runs on bare metal and wasm, a thread pool, timers and async TCP.

A standard library for real programs

30+ generic, void*-free modules: collections, a web stack (HTTP server, router, WebSocket, gzip), a GUI toolkit, real-time audio and MIDI, ML tensors on CPU and GPU, crypto, serialization.

Bugs C compiles, SafeC rejects

Each pair below is the same mistake. The C version builds without complaint and fails at run time — maybe. The SafeC version doesn't build, and the compiler says why.

C — compiles, returns a dangling pointer

c
int* counter() {
    int x = 42;
    return &x;   // warning at best; UB when used
}

SafeC — compile-time error

c
&stack int counter() {
    int x = 42;
    return &x;
}
text
error: cannot return '&stack int': stack reference escapes function scope

C — a null pointer is just a pointer

c
struct Node { int value; };

int read(struct Node* n) {
    return n->value;   // crashes when n == NULL
}

SafeC — nullable references must be checked

c
struct Node { int value; };

int read(?&Node n) {
    return n.value;
}
text
error: dereference of nullable reference '?&Node' requires 'unsafe', or use match/is_null()/.default(value) instead

C — silent truncation

c
long big = 5000000000L;
int small = big;   // implementation-defined result

SafeC — narrowing is explicit

c
long big = 5000000000L;
int small = big;
text
error: type mismatch in variable declaration: cannot convert 'long' to 'int'

More comparisons with C, C++, Rust and Zig →

Batteries included

The standard library is written in SafeC, links only the modules a program uses, and ships prebuilt for every release platform.

Web server

c
#include <std/http/router.h>

struct HttpResponse hello(&HttpRequest req, &HttpCtx ctx) {
    struct String body = std::String::from("Hello, ");
    body.push(ctx.param_or("name", "world"));
    struct HttpResponse r = std::HttpResponse::text(200, body.as_ptr());
    body.free();
    return r;
}

int main() {
    struct Router app = std::Router::new();
    defer app.drop();
    app.use_logger();
    app.use_gzip(1024UL);
    app.get("/hello/:name", hello);
    app.static_dir("/assets", "./public");

    struct HttpServerConfig cfg = std::HttpServerConfig::defaults(8080);
    return app.listen(&cfg);    // keep-alive HTTP/1.1 on the I/O reactor
}

Router, middleware, HTTPS, static files, cookies, multipart, SSE, WebSocket and a redirect-following client with gzip — HTTP & Web.

GUI

c
#include <std/gui/gui_app.h>

static void on_click(&Widget w, ?&Widget label) {
    match (label) { case some(l): { l.set_text("Clicked!"); } case null: { } }
}

int main() {
    struct GuiApp app = GuiApp::new();
    ?&GuiWindow ow = app.open_window("Hello", 360, 200);
    match (ow) {
        case null: { app.free(); return 1; }     // no display
        case some(win): {
            &Widget root = Widget::vstack().set_padding(16);
            &Widget label = Widget::label("Hello from SafeC");
            root.add_child(label);
            root.add_child(Widget::button("Click me").on_click(on_click, label));
            app.set_root(win, root);
            app.run();                           // until the window closes
        }
    }
    app.free();
    return 0;
}

An anti-aliased vector canvas with TrueType text and 30+ themed widgets on X11, Wayland, Win32, Cocoa and the web (WebAssembly) — GUI.

Async

c
#include <std/async/async.h>
#include <std/async/host.h>
extern int printf(const char* fmt, ...);

struct Countdown {
    int st; int i; struct Sleep d;
    struct Poll<int> poll(&Context cx);
    void drop();
};
struct Poll<int> Countdown::poll(&Context cx) {
    ASYNC_BEGIN(self.st, 1);                 // one await point
    for (self.i = 3; self.i > 0; self.i = self.i - 1) {
        self.d = Sleep::new(100LL);
        AWAIT(1, self.d);                    // Pending until the timer fires
        printf("%d\n", self.i);
    }
    ASYNC_RETURN(42);
    ASYNC_END;
}
void Countdown::drop() { if (self.st == 1) self.d.drop(); }

int main() {
    struct Countdown c; c.st = ASYNC_INIT;
    int v = block_on(&c);                    // or LocalExecutor / ThreadPool / Runtime
    c.drop();
    return v == 42 ? 0 : 1;
}

Futures, wakers, join/select/timeout, channels and async TCP over epoll, kqueue and WSAPoll — Async. (The ASYNC_* coroutine macros need compat_preprocessor = true.)

Audio

c
#include <std/audio/audio.h>
#include <std/math.h>

struct Tone { double phase; double step; };

// The real-time callback: fill the output buffer, never allocate or block.
int render(&AudioIo io, ?&Tone tp) {
    match (tp) {
        case some(t): {
            []float out = io.output_f32();   // frames x channels, interleaved
            for (unsigned int f = 0U; f < io.frames; f = f + 1U) {
                float v = (float)(0.2 * std::sin_d(t.phase));
                out[(unsigned long)f * 2UL] = v;           // bounds-checked
                out[(unsigned long)f * 2UL + 1UL] = v;
                t.phase = t.phase + t.step;
            }
        }
        case null: {}
    }
    return AUDIO_CB_CONTINUE;
}

Real-time audio and MIDI on ALSA, JACK, Core Audio, WASAPI and Web Audio — Audio & MIDI.

Bare metal & wasm

c
#define LED_PORT ((unsigned int*)0x40020014)

// safec --target=arm-7em --freestanding blink.sc
interrupt void systick_handler() {
    unsafe { volatile_store(LED_PORT, volatile_load(LED_PORT) ^ 1U); }
}

// safec --target=wasm32 fib.sc  ->  a module exporting fib()
__attribute__((export_name("fib")))
int fib(int n) { return n < 2 ? n : fib(n - 1) + fib(n - 2); }

The whole standard library builds for every bare-metal target; std/wasm drives the DOM from SafeC — Bare-Metal, WebAssembly & DOM.

Machine learning, CPU to GPU

std/ml trains and runs neural networks in SafeC itself — tensors with autograd, the layers today's models are built from, and GPU backends — with no Python and no framework runtime.

  • Tensors and autograd. DenseTensor<E> with reverse-mode autograd, stored as float32 (FloatTensor), fp16 (HalfTensor) or bf16 (BF16Tensor) and computed in float32.
  • Layers and models. MLPs, losses and optimizers from SGD to AdamW and Lion; multi-head, flash and advanced attention (MLA, Gated DeltaNet); DiT and JiT transformers, RNNs and xLSTM, Mamba, CNNs and ResNet-style ConvNets, U-Net and diffusion, autoencoders, GANs, reinforcement learning and LoRA / QLoRA fine-tuning.
  • One kernel set, every GPU. The same training code runs on the CPU (built-in GEMM or a vendor BLAS) or on a tensor device: CUDA, ROCm, Metal, Vulkan (SPIR-V) or WebGPU, all from one kernel source, with fp16 and bf16 mixed precision.
  • Measured. Against PyTorch's best configuration, SafeC trains a small MLP 2.6× (MI100) and 7.8× (H200) faster and decodes Gemma 4 E2B 3.1× and 1.5× faster; on an M1 Pro, MLX and PyTorch still win some tests. See the benchmarks.
c
#include <std/ml/module.h>
#include <std/ml/datasets.h>
#include <std/ml/trainer.h>
extern int printf(const char* fmt, ...);

// The forward pass builds the autograd graph as it goes.
static &FloatTensor clf_loss(&Mlp m, const &FloatTensor x,
                             const &FloatTensor y) {
    return tensor_cross_entropy_loss(m.forward(x), y);
}
static &FloatTensor clf_logits(&Mlp m, const &FloatTensor x) {
    return m.forward(x);
}

int main() {
    struct Rng rng = Rng::new(1UL);
    struct Dataset data = Dataset::moons(400UL, 0.1f, &rng);
    defer data.drop();
    struct DatasetSplit sp = data.split(0.25f, &rng);
    defer sp.train.drop();
    defer sp.test.drop();
    struct Mlp net = Mlp::new(2UL, 32UL, 2UL, 2UL, ACT_RELU, ACT_NONE, &rng);
    defer net.drop();
    struct Optimizer opt = Optimizer::new(OPT_ADAM, 0.01f);
    defer opt.drop();
    model_add_params(&net, &opt);

    struct TrainConfig cfg = TrainConfig::default();
    cfg.epochs = 60UL;
    cfg.device = device_auto();   // a GPU if built in, else the CPU
    trainer_fit(&net, clf_loss, &opt, &sp.train, &cfg);
    float acc = trainer_accuracy(&net, clf_logits, &sp.test, 64UL);
    printf("accuracy %.2f on %s\n", acc, device_name(cfg.device));
    return 0;
}

Install

macOS & Linux

bash
curl -fsSL https://raw.githubusercontent.com/safec-org/SafeC/main/install.sh | bash

Windows

powershell
irm https://raw.githubusercontent.com/safec-org/SafeC/main/install.ps1 | iex

Then create a project and run it:

bash
safeguard new hello && cd hello
safeguard run

SafeC needs clang 21 or newer to compile and link (the installer finds it or tells you how to get it). See Install for options, requirements and building from source.

Get going

Learn

The Book, the guide, the language reference and the standard library, in one place.

Playground

Edit and run SafeC in your browser, load the Book's examples and share a program as a link.

Tools

safeguard builds, tests, formats and lints; sc-lsp brings diagnostics and completion to your editor.

Blog

Release notes and news. Follow the Atom feed.

Community

Report bugs, propose features and send pull requests on GitHub.

GitHub

The compiler, the standard library, safeguard and the install scripts — MIT licensed.