個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證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.
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 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. |