a13n
Build AI agents and run them on cloud.
SafeC is a region-aware evolution of C with compile-time memory safety, no garbage collector, zero hidden costs and full C ABI compatibility.
Developer toolsC++ · ScalaGitHub @safec-orgOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
SafeC is an evolution of C — preserving C ABI compatibility — while enforcing memory safety, type safety, and real-time determinism at compile time with zero runtime overhead.
Taken from the repository README
Loading the full README.
Read the full README ↗A deterministic, region-aware, compile-time-first systems programming language.
SafeC is an evolution of C — preserving C ABI compatibility — while enforcing memory safety, type safety, and real-time determinism at compile time with zero runtime overhead.
Documentation | Language Reference | Standard Library
Installs a prebuilt safec/safeguard/sc-lsp release from GitHub Releases (the newest one, pre-releases included):
curl -fsSL https://raw.githubusercontent.com/safec-org/SafeC/main/install.sh | bash
irm https://raw.githubusercontent.com/safec-org/SafeC/main/install.ps1 | iex
bash install.sh --prefix=/opt/safec # or specify install directory
bash install.sh --version=v1.0.0-beta-7 # or pin a specific release
bash install.sh --std=core,collections,gui # only these prebuilt std sections (default: all)
bash install.sh --std=+ml # add a section to an existing install later
Requirement: clang 21 or newer. safec emits LLVM IR, and safec --emit-bin and safeguard hand it to clang to compile and link (clang also imports C headers). An older clang can't read that IR. The install scripts find a suitable clang, including versioned names like clang-21, apt.llvm.org's /usr/lib/llvm-21 and Homebrew's llvm. If it isn't the plain clang on your PATH, they set SAFEC_CLANG/SAFEC_CLANGXX to it; if there's none, they print how to install one:
| Platform | Install clang |
|---|---|
| macOS | brew install llvm (Apple's Xcode clang is too old) |
| Debian / Ubuntu | wget https://apt.llvm.org/llvm.sh && sudo bash llvm.sh 21 |
| Windows | winget install LLVM.LLVM |
| Other | LLVM releases |
Published binaries: macOS arm64 (macOS 11 or newer), Linux x86_64 (glibc 2.25 or newer: Debian 10, Ubuntu 18.04, RHEL 8 and later; not musl), Windows x86_64. Each release also ships the standard library prebuilt for its platform, one archive per section (lib/<triple>/std/core.a, collections.a, gui.a, ml.a, ...: std's top-level directories, core for the files directly in std/). safeguard links only the sections holding the std modules a project uses, instead of compiling them, when they match the installed safec; a section left out with --std (-Std for install.ps1) is compiled from source when a project needs it. std is always compiled optimized, in debug builds too. See Releases for the full asset list, or .github/workflows/release.yml for how they're built. To build the compiler, stdlib, safeguard, or sc-lsp from source instead (e.g. for a platform the release workflow doesn't publish, or from an unreleased commit), see each component's own CMakeLists.txt under compiler/, safeguard/, and the sibling sc-language-server repo.
Then create a project and run:
safeguard new hello && cd hello
safeguard run
// hello.sc
extern int printf(const char* fmt, ...);
region AudioPool { capacity: 65536 }
struct Sample { float left; float right; };
int main() {
// Region-safe reference
int x = 42;
&stack int ref = &x;
// Arena allocation — deterministic, no malloc
&arena<AudioPool> Sample s = new<AudioPool> Sample;
s.left = 0.5;
s.right = -0.3;
printf("L=%.2f R=%.2f\n", s.left, s.right);
arena_reset<AudioPool>();
// Bounds-checked slice
int arr[5];
arr[0] = 10; arr[1] = 20; arr[2] = 30;
[]int sl = arr[0..3];
printf("slice len = %ld\n", sl.len);
return 0;
}
| Feature | Description |
|---|---|
| Region-Based Memory Safety | stack, heap, arena<R>, static regions enforced at compile time. No GC, no runtime metadata. |
| Zero Hidden Cost | No implicit allocations, no hidden runtime, no background GC, no implicit exceptions. |
| C ABI Compatible | C struct layout, C calling conventions, native #include <stdio.h>. Link SafeC objects into any C or C++ project. |
| Full C Superset | __attribute__, C-style function pointers, bitfields, designated initializers, flexible array members, anonymous struct/union, compound literals, _Generic, VLAs (gated to unsafe{}). |
| Compile-Time First | const fn/consteval/constinit, static_assert, if const, generics via monomorphization. |
namespace | C++-style namespace std { ... } blocks — qualified lookup, mangled linkage, unqualified-fallback resolution. Types are not namespaced by design. |
| Native SIMD | vec<T, N> — a first-class vector type lowering directly to LLVM vector IR; portable std::simd library with per-ISA convenience headers for x86_64 (SSE/AVX), AArch64 (NEON), RISC-V (RVV), WebAssembly (SIMD128), SPIR-V, ARM Cortex-M (MVE + DSP extension), CUDA (NVPTX), and ROCm (AMDGPU). |
| Multi-Target Codegen | --target <triple> cross-compiles to any LLVM-supported architecture/OS; named aliases (arm-7em, rv32i, wasm32, …) pick the triple, CPU, features and ABI for bare-metal and WebAssembly — see the target matrix below. |
| Bare-Metal Ready | 14 no-OS targets (x86, AArch32/64, Cortex-M0…M33, RV32/RV64 I/E) tested in an emulator, naked/interrupt functions, inline assembly, volatile MMIO (variables, fields, pointees), atomic built-ins, clang-compatible architecture macros, ARM Cortex-M HAL (NVIC/SysTick/SCB) and DSP-extension intrinsics. |
| WebAssembly | / builds library modules; // attributes define the module's interface, and supplies a freestanding runtime, DOM and browser-API interop (events, timers, fetch, storage, history) with its JS glue, and signal-driven hydration. |
SafeC enforces these safety properties entirely at compile time:
| Property | Guarantee |
|---|---|
| Spatial safety | Bounds-checked array/slice access (static when possible, runtime otherwise) |
| Temporal safety | No use-after-free — region escape analysis prevents dangling references |
| Aliasing discipline | One mutable XOR many shared references (borrow checker with NLL) |
| Region escape | References cannot outlive their region; inter-procedural analysis at call sites |
| Resource safety | Values of a Drop type are dropped or moved exactly once on every path: leaks, use after drop/move and double drops are compile errors (no run-time cost) |
| Data race freedom | Types are Send/Sync by structure; spawn, spawn_scoped, channels, thread_spawn and Mutex<T> (guard-only access) accept only what is safe to share |
| Null safety | References are non-null by default. Nullable references (?®ion T, and the region-less ?&T outliving reference — see below) and ?T optionals cannot be dereferenced, member-accessed, or force-unwrapped directly — only is_null()/is_none(), .default(fallback), match, or an explicit unsafe {} block can read one |
| Raw pointers | Safe code can hold, compare and pass a raw T* to C, but not use it. Accessing through it, pointer arithmetic, making one from an integer, reinterpreting it as another pointer type, and turning it into a non-null &T all need unsafe. (?&T)p is allowed: it gives a nullable reference that must be matched before use |
| Determinism | No hidden allocator, no GC, no implicit nondeterminism in the safe fragment |
Inside unsafe {}, all properties become the programmer's responsibility. The boundary is explicit and auditable — raw-pointer aliasing (WAR/RAW/WAW hazards) and raw-pointer subscript bounds are two examples of not being tracked in unsafe code, by design, the same way real C leaves them to the programmer there. unsafe code also follows C's strict aliasing rule: reinterpret memory as another type through char* or memcpy, not a pointer cast, or build with --no-strict-aliasing (see Safety).
Heap allocator misuse — double-free, use-after-free, freeing a pointer a different allocator produced, and freeing NULL — sits partly inside and partly outside the table above. Use-after-free and double-free through &heap and &arena<R> references are caught at compile time by a dataflow analysis over every function: it follows a pointer through any number of copies, struct fields and array elements, follows control flow exactly (branches, ?:, loops to a fixpoint, break/continue/return/try, defer), tracks each arena_mark separately, and summarizes every function with a body (what it frees, resets or returns) so a free inside a callee is checked at the call site — see Memory & Regions. It trusts calls through function pointers, functions without a body (externs such as free()), and memory reached through raw pointers in unsafe code (std containers' buffers). std::alloc/std::dealloc catch the rest at runtime instead, for a fixed, small cost: every allocation carries a 16-byte header tagging it live/freed, dealloc()/realloc_buf() check the tag and abort with a diagnostic on a double-free or a pointer alloc() never returned, dealloc(NULL) is a safe no-op, and a bounded quarantine (freed blocks aren't handed back to the system allocator for a further ~64 frees) keeps that check reliable even though this platform's allocator overwrites a freed block's first bytes almost immediately. std/alloc/pool.h/slab.h/tlsf.h's allocators get the equivalent runtime check using their own existing per-block metadata; freeing a pointer through the wrong allocator's free function (mismatched free) is also runtime-only, since the compiler doesn't track which allocator produced a given pointer. Leaks are checked for owning types: a value of a type with a drop() method (every std container, file, socket, thread, guard and handle) must be dropped or moved on every path — see Ownership & Drop. A bare &heap T that no such type owns isn't tracked for leaks; wrap it in one, or pair it with defer std::dealloc(...).
Formal treatment uses syntactic type safety (Wright & Felleisen) with region semantics drawn from Oxide (Weiss et al., 2019) and Cyclone (Grossman et al., 2002).
| Directory | Description |
|---|---|
compiler/ | SafeC → LLVM IR compiler (C++17, ~19K LOC), with IR regression tests in compiler/tests/ |
std/ | Standard library modules (.h declarations + .sc implementations), including std/simd/ (portable vec<T,N>-based SIMD + per-ISA convenience headers) and std/hal/ (per-architecture bare-metal HAL) |
safeguard/ | Package manager and build system — compiles and links mixed SafeC/C/C++/Objective-C projects, and bundles apps |
tests/stdlib/ | Standard library test suites: hosted (safeguard test), bare-metal emulator/WebAssembly (baremetal/), std/audio's Web Audio/Web MIDI glue under Node (audio_web/), Win32 and X11 backends (win32/, x11/), and the GPU devices (device/ on any device, rocm/, cuda/, mps/, vulkan/, webgpu/) |
tests/std_modules/ | Checks that every std header's functions are implemented by the std modules a build including it links |
examples/ | Example projects, including a full-stack HTTP server + WebAssembly client, an async HTTP server/client demo (async_demo) a GUI widget gallery (gui_gallery, also in the browser) and std/gui on the web (gui_web) |
Source (.sc) → Preprocessor → Lexer → Parser → AST → Sema → ConstEval → CodeGen → LLVM IR
safec <input.sc> [options]
Output:
-o <file> Output file
--emit-llvm Emit LLVM IR text (default: bitcode)
--emit-bin Compile and link a binary/library via clang
(-l/-L, --shared, --static-lib, --lto apply here)
--dump-ast Dump AST and exit
--dump-pp Dump preprocessed source and exit
Optimization:
-O0 -O1 -O2 -O3 -Os -Oz Optimization level (default -O0); --release = -O2
--no-strict-aliasing Don't emit type-based alias info (unsafe code
that type-puns through pointer casts)
Compilation:
--no-sema Skip semantic analysis
--no-consteval Skip const-eval pass
--freestanding Freestanding mode (no hosted headers, warns on stdlib)
--provides=<f,g> Functions defined in another file (no freestanding warning)
--compat-preprocessor Enable full C preprocessor
-j <N> Parallel jobs for multiple input files
Targets:
--target <alias|triple> Cross-compile (default: host): a bare-metal or
wasm alias (see --list-targets) or any LLVM triple
--target-cpu <name> Refine the CPU
--target-features <+a,-b> Refine ISA features (LLVM -mattr syntax)
--target-abi <name> Refine the ABI
--list-targets Print the alias table
--print-target-clang-flags clang flags matching a --target
Preprocessor:
-I <dir> Add include search path
-D NAME[=VALUE] Define preprocessor macro
--no-import-c-headers Disable automatic C header import via clang
Debug:
--g lines Line-table debug info
--g full Full debug info (lines + local variables)
Incremental:
--no-incremental Disable file-level bitcode cache
--cache-dir <dir> Cache directory (default: .safec_cache)
--clear-cache Clear cached .bc files
--target <triple> selects any LLVM-registered target; SafeC hardcodes
nothing architecture-specific — vec<T,N> and the rest of codegen lower to
portable LLVM IR, and the target's own backend does instruction selection.
Verified with real generated machine code (not just accepted input) across:
| OS | Architectures |
|---|---|
| macOS | x86_64, AArch64 |
| Linux | x86_64, x86, AArch64, Aarch32 (ARMv7), RV64, RV32 |
| Windows (MSVC) | x86_64, x86, AArch64 |
| iOS | AArch64 (device + simulator) |
| Android | AArch64, Aarch32, x86_64, x86 |
| FreeBSD | x86_64, AArch64 |
Bare metal (--target=<alias>) | x86, x86_64, AArch32, AArch64, Cortex-M0/M3/M4/M7/M33, RV32I/E, RV64I/E — the whole stdlib builds and a 62-check suite (including the async LocalExecutor) runs in the Unicorn emulator for each |
WebAssembly (--target=wasm32/wasm64) | Library modules with export_name/import_* attributes; same 62-check suite runs under Node |
| Portable / GPU | SPIR-V, CUDA (NVPTX), ROCm (AMDGPU; the std::ml ROCm backend is tested on an MI100) |
Metal Shading Language is not supported — Apple's Metal compiler has no
LLVM backend upstream (it's a separate, closed toolchain), unlike NVPTX/
AMDGPU/SPIR-V which are real LLVM targets. The only practical interop path
from SPIR-V output is a third-party translator (e.g. SPIRV-Cross), not
something safec does itself. (std::ml's tensor devices reach Apple GPUs,
and any Vulkan or WebGPU implementation, through their own shader toolchains:
its kernels are compiled at run time as MSL on Metal, embedded as SPIR-V for
Vulkan, and written in WGSL for WebGPU.)
ARM Cortex-M gets first-class treatment: cross-compiles cleanly with
--freestanding, has a HAL (std/hal/cortex_m.sc — NVIC, SysTick, SCB),
and exposes the DSP extension's packed-SIMD/saturating instructions
(SADD16, SMLAD, USAD8, SSAT, ...) as std::dsp_* builtins on M4/M7, plus
real MVE vector codegen through vec<T,N> on M55/M85.
&stack int ref = &x; // stack reference (lexical scope)
&heap float hp = new float; // heap reference (explicit free)
&arena<AudioPool> Sample s = new<AudioPool> Sample; // arena reference
&static Config cfg = &global_config; // static reference (program lifetime)
?&stack Node next; // nullable reference
?&Handle outliving; // outliving reference — no region at all
&Point sumPoint(&Point p); // non-nullable, region left to the caller
auto x = compute_something(); // type inferred from the initializer
An arena region (region AudioPool { capacity: 65536 }) lazily allocates its backing buffer on first new<AudioPool>. arena_reset<AudioPool>() rewinds the bump pointer for reuse (the buffer itself stays allocated); arena_destroy<AudioPool>() actually frees the buffer — safe to call more than once (a no-op past the first call) or before any allocation has happened, and a later new<AudioPool> after destroying transparently re-allocates.
&T / ?&T with no region)Leaving off the region qualifier — &T (non-nullable) or ?&T (nullable) — gives a reference with no declared or tracked region. Two overlapping reasons to reach for it:
extern boundary. extern declarations (SafeC's only linkage form — always C ABI, no name mangling) use plain T* for pointer parameters/returns, same as C. Most of the time a raw pointer only needs to survive the one call it's used in, so an ordinary unsafe {}-gated T* is enough. But sometimes a pointer crossing that boundary is meant to be retained by the C side past the current function returning — a callback's registered context, an opaque handle a C API hands you and expects back later — and neither &stack T (dies with this scope, can't be handed to something that outlives it) nor &heap/&static T (both claim an ownership/lifetime guarantee SafeC has no way to verify across an ABI boundary it doesn't control) are the right fit for that value while it's on the SafeC side.&stack, &heap, &static, or &arena — picking one specific region as the parameter type only narrows who can call it, for no real safety benefit. Default to the region-less form unless the parameter's own semantics genuinely pin it to one lifetime (e.g. a callee that stores the reference past its own return needs &heap/&static, not &stack).Nullable vs. non-nullable is the ordinary, orthogonal choice here too — use ?&T only where the value can genuinely be absent, &T otherwise:
extern int c_register_callback(struct Widget* w); // conceptually retains 'w'
int use_outliving(?&Widget w) {
match (w) {
case null: return -1;
case some(v): return v.value; // 'v' is a plain 'Widget' value, no unsafe
}
}
struct Widget local;
?&Widget wref = &local; // &stack T → ?&T: implicit, no unsafe, no region check
use_outliving(wref);
c_register_callback(wref); // ?&T → T*: implicit, no unsafe
A reference of any region converts to either form implicitly (&stack Widget → &Widget/?&Widget, no cast), and &T/?&T converts back to T*/void* implicitly. A raw T* (from any extern call) converts to ?&T anywhere, but to a non-null &T only inside unsafe, since nothing checks it isn't null. There's no region bookkeeping, because the region-less form doesn't claim a region to check: Region::Extern is exempt from every escape/lifetime check by construction (see Type.h's Region::Extern for the full rationale). A non-nullable &T derefs/member-accesses freely, same as any other non-nullable reference; reading a nullable ?&T out still goes through the same grammar as ?&stack T: match, is_null(), .default(fallback), or an unsafe-gated !.
&T/?&T needs a concrete T. Where one container must hold callbacks with different context types at once (a widget tree, a timer wheel, a VFS mounting several drivers), the slot stores the pair type-erased, but the only way to fill it is a generic method taking a matching fn(?&T) and ?&T (Widget::on_click, TimerWheel::add, Vfs::mount, TaskScheduler::spawn), so every call is still type-checked. Where one T per instance fits, the type is simply generic (std/dma.h's generic<T> struct DmaChannel).
generic<T: Numeric> T add(T a, T b) { return a + b; }
int r = add(3, 4); // monomorphized to add_int
double d = add(1.0, 2.0); // monomorphized to add_double
A trait declares a method signature set; a struct satisfies it structurally — just by defining methods with matching names/signatures, no impl Trait for Type block. Traits are used as generic bounds (generic<T: Drawable>), checked at the call site during monomorphization.
trait Drawable {
void draw() const;
}
struct Circle {
double radius;
void draw() const; // satisfies Drawable structurally — nothing else needed
}
void Circle::draw() const { printf("circle r=%.1f\n", self.radius); }
generic<T: Drawable> void render(T shape) { shape.draw(); }
render(circle); // OK: Circle has a matching draw() const
Built-in traits usable as bounds: Eq, Ord, Add, Sub, Mul, Div, Numeric (int/float family), plus the structural Indexed/Pointer traits satisfied automatically by array/slice and pointer/reference types.
newtype wraps a base type in a distinct one — same representation, but not implicitly interchangeable with the base type or other newtypes over it (this is how std::dsp's Fixed Q8.24 type keeps itself from being accidentally mixed with a plain int):
newtype UserId = int;
UserId id = (UserId)42; // explicit cast required both ways
Tuples are tuple(T1, T2, ...), constructed with a parenthesized literal and accessed by position (.0, .1, ...):
tuple(int, double) p = (42, 3.14);
int x = p.0;
double y = p.1;
Binary operators (+, *, comparisons, ...) require both operands to
already be the same type — SafeC never does C's "usual arithmetic
conversions" to silently reconcile mismatched signedness. Assignment and
argument-passing are more permissive: a value implicitly widens to any
larger numeric type, including crossing from integer to floating-point
(int → long, float → double, int → double). Anything that
isn't a pure widening — narrowing (long → int), or float → integer in
either direction — requires an explicit cast:
int x = 42;
double dx = x; // implicit: int -> double widens safely
long long big = 100LL;
int small = (int)big; // explicit cast required: this narrows
double pi = 3.14;
int truncated = (int)pi; // explicit cast required: float -> int narrows
namespace std {
void log(const char* msg);
}
std::log("hello"); // or just log("hello") — unqualified fallback resolves too
#include <std/simd/simd.h> // portable f32x4/i32x4/... type aliases
vec<float, 4> a = {1.0, 2.0, 3.0, 4.0};
vec<float, 4> b = simd_splat_f32x4(10.0);
vec<float, 4> c = a + b; // lowers directly to LLVM vector IR —
// real SSE/NEON/RVV/SIMD128 instructions
// depending on --target, no intrinsics needed
struct Vec2 {
double x; double y;
double length() const;
Vec2 operator+(Vec2 other) const;
};
double Vec2::length() const { return self.x * self.x + self.y * self.y; }
Vec2 Vec2::operator+(Vec2 other) const {
Vec2 r; r.x = self.x + other.x; r.y = self.y + other.y; return r;
}
match (status) {
case 200: printf("OK\n");
case 404: printf("Not Found\n");
case 400..499: printf("Client Error\n");
default: printf("Unknown\n");
}
tryA plain T implicitly wraps to ?T ("present"); null is the empty case for both ?T and ?®ion T. Reading one back requires is_null()/is_none(), .default(fallback), match, or unsafe {} — direct dereference/member-access/! force-unwrap outside those is a compile error.
?int find(int *arr, int n, int target) {
int i = 0;
while (i < n) {
int v; unsafe { v = arr[i]; }
if (v == target) return v; // implicit T -> ?T wrap
i = i + 1;
}
return null; // the empty case
}
int val = try find(arr, n, 42); // propagates the empty case to the caller
int safe = find(arr, n, 42).default(-1);
match (find(arr, n, 42)) {
case none: printf("not found\n");
case some(v): printf("found %d\n", v);
}
FILE *f = fopen(path, "r");
defer fclose(f);
char *buf = malloc(4096);
defer free(buf);
// both run in LIFO order at function exit
?&heap int p = malloc_defer(64UL); is sugar for an allocation plus a matching defer free in one statement — malloc_defer isn't a real callable (nothing to look up or link), it's recognized only as a variable declaration's initializer and expands at parse time to std::alloc(n) (the generic allocator, T taken from the declared type) and defer std::dealloc(p);; with auto (no type to take T from) it is the type-erased std::alloc_raw_(n) / std::dealloc_raw_(p) pair, and p is a byte pointer (unsigned char*).
void* worker(void* arg) { printf("hello from thread\n"); return (void*)0; }
long long h = spawn(worker, 0);
join(h);
Channels — std/sync/channel.h's Chan<T> is a typed, bounded, blocking MPMC channel over the compiler's untyped chan_create/chan_send/chan_recv/chan_close built-ins (sizeof(T) <= 8, checked at compile time; send a ?&T for anything larger):
#include <std/sync/channel.h>
struct Chan<int> ch = std::Chan::new(4); // capacity 4
ch.send(42); // blocks while full; 0 once closed
match (ch.recv()) { // blocks while empty; null once closed and drained
case some(v): printf("got %d\n", v);
case none: printf("closed\n");
}
ch.close();
Async — std/async is Rust's futures model as a library: a future is a struct whose poll(&Context cx) returns Poll<T>, and the coroutine macros turn poll into a resumable state machine (the resume point and every local that lives across an await are fields of the struct; resumption is a switch that gotos back to the await point):
#include <std/async/async.h>
#include <std/async/host.h>
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(); }
struct Countdown c; c.st = ASYNC_INIT;
int v = block_on(&c); // or spawn on LocalExecutor / ThreadPool / Runtime
For single-consumer fan-in from multiple producer threads without the channel runtime's blocking/condvar overhead, std/sync/mpsc.h provides a spinlock-guarded bounded ring buffer (struct MpscQueue<T> q = std::MpscQueue::new(64UL); q.enqueue(v); q.dequeue() → ?T); std/sync/lockfree.h's LFQueue<T> (LFQueue::new) is the single-producer/single-consumer case with no locking at all. For communicating with a different process rather than another thread, see std/ipc/pipe.h (anonymous pipes, typically parent/child after fork()) and std/ipc/uds.h (named Unix domain sockets, for unrelated processes).
naked void _start() { asm volatile ("mov $stack_top, %rsp"); }
interrupt void timer_isr() { unsafe { volatile_store((int*)0x40000000, 1); } }
section(".rodata") const int MAGIC = 42;
switch/case with fall-through not supported (use match)--target (see "Target Support")thread_local / _Thread_local / __thread supported on globals and static locals__stdcall, __cdecl, __fastcallstd/async provides futures, executors and goto-based coroutines as a library)Released under the MIT License. Copyright 2024-2026 SafeC Contributors.
--target=wasm32wasm64export_nameimport_moduleimport_namestd/wasm| Modern Language | Generics, struct methods, operator overloading, pattern matching, optional types, slices, defer, tuples, typed channels. |
| Borrow Checker | Mutable-XOR-shared aliasing discipline with non-lexical lifetimes (NLL). |
| Standard Library | 30+ modules, generic and void*-free: mem, io, files and directories (dir, path), str, math, collections (vec, map, list, bst, ...), thread, atomic, simd, serialization (JSON/XML/HTML/YAML/protobuf, CommonMark + GFM Markdown), a web stack (keep-alive HTTP server with router + middleware, static files, cookies, multipart, SSE, WebSocket, a redirect-following client, URL parsing, gzip; HTTPS and wss://), TLS (the OS's library — OpenSSL loaded at run time, SChannel, SecureTransport — or SafeC's own TLS 1.3 client and server), crypto (SHA-1/2, HMAC, HKDF, AES-GCM, ChaCha20-Poly1305, X25519, P-256/P-384 ECDH + ECDSA, RSA PKCS#1/PSS, X.509 path validation), a cooperative task scheduler + real-time I/O reactor, Rust-style async (futures, executors, async TCP), real-time audio and MIDI I/O (ALSA, JACK, Core Audio, WASAPI, Web Audio; the ALSA sequencer, Core MIDI, WinMM, Web MIDI), and ML tensors that train on the CPU (with BLAS) or on a tensor device: ROCm, CUDA, Metal, Vulkan (SPIR-V) or WebGPU, all from one kernel set. std/gui is a GUI toolkit: an anti-aliased vector canvas with TrueType text, multi-window apps on X11, Wayland, Win32, Cocoa and the web (WebAssembly <canvas>, with IME text input and DOM interop), optional GPU presentation (OpenGL ES/EGL, Metal, Direct3D 11, WebGL), and 30+ themed widgets. |
| Async/Await (library) | std/async: Rust's Future/Poll/Waker/Context model without language coroutines — stackless state machines written with ASYNC_BEGIN/AWAIT macros over goto (the compiler rejects a local that would live across an await), a deterministic no-heap single-threaded executor that runs on bare metal and wasm32 too, a multi-threaded ThreadPool, timers, join/select/timeout combinators, channels, Mutex/Semaphore/Notify/cancellation, and async TCP on the epoll/kqueue/WSAPoll reactor. |
| Package Manager | safeguard — project scaffolding, dependency resolution, build orchestration, mixed SafeC/C/C++/Objective-C compilation and linking, plus format (reindenter), lint (static analysis), check (fast compile-only), and test (build + run tests/). It builds C/C++/Objective-C packages and existing C/C++ libraries (plain sources or CMake) as dependencies; libraries export their headers to build/include (Windows DLLs with an import library); safeguard bundle packages GUI apps as a macOS .app, a Windows app folder, a Linux .deb or (--target wasm32) a web page with its JS glue. It compiles and links only the std modules a project includes, with the system libraries and frameworks those modules need. safeguard doc generates a library's API reference (rustdoc-style pages, search, source links; built by safec-doc, a SafeC program) and safeguard publish prepares its repository for publishing (CI for GitHub, GitLab, Gitea or Forgejo: tests on three OSes, prebuilt libraries on releases, docs on Pages). |
| LSP Support | Language server sharing the compiler's own frontend (always in sync with the language); diagnostics, hover, completion, go-to-definition; VS Code extension (.sc and .h), Neovim, Emacs. |