Zen C is a modern systems programming language that compiles to human-readable GNU C/C11. It provides a rich feature set including type inference, pattern matching, generics, traits, async/await, and manual memory management with RAII capabilities, all while maintaining 100% C ABI compatibility.
Join the discussion, share demos, ask questions, or report bugs in the official Zen C Discord server!
- Discord: Join here
- Overview
- Community
- Quick Start
- Language Reference
- 1. Variables and Constants
- 2. Primitive Types
- 3. Aggregate Types
- 4. Functions & Lambdas
- 5. Control Flow
- 6. Operators
- 7. Printing and String Interpolation
- 8. Memory Management
- 9. Object Oriented Programming
- 10. Generics
- 11. Concurrency (Async/Await)
- 12. Metaprogramming
- 13. Attributes
- Custom Attributes
- Smart Derives
- 14. Inline Assembly
- 15. Build Directives
- 16. Keywords
- Standard Library
- Tooling
- Compiler Support & Compatibility
- Contributing
- Attributions
git clone https://github.com/z-libs/Zen-C.git
cd Zen-C
make
sudo make installZen C can be compiled as an Actually Portable Executable (APE) using Cosmopolitan Libc. This produces a single binary (.com) that runs natively on Linux, macOS, Windows, FreeBSD, OpenBSD, and NetBSD on both x86_64 and aarch64 architectures.
Prerequisites:
cosmocctoolchain (must be in your PATH)
Build & Install:
make ape
sudo env "PATH=$PATH" make install-apeArtifacts:
out/bin/zc.com: The portable Zen-C compiler. Includes the standard library embedded within the executable.out/bin/zc-boot.com: A self-contained bootstrap installer for setting up new Zen-C projects.
Usage:
# Run on any supported OS
./out/bin/zc.com build hello.zc -o hello# Compile and run
zc run hello.zc
# Build executable
zc build hello.zc -o hello
# Interactive Shell
zc replYou can set ZC_ROOT to specify the location of the Standard Library (standard imports like import "std/vec.zc"). This allows you to run zc from any directory.
export ZC_ROOT=/path/to/Zen-CZen C distinguishes between compile-time constants and runtime variables.
Values that exist only at compile-time (folded into code). Use these for array sizes, fixed configuration, and magic numbers.
def MAX_SIZE = 1024;
let buffer: char[MAX_SIZE]; // Valid array size
Storage locations in memory. Can be mutable or read-only (const).
let x = 10; // Mutable
x = 20; // OK
let y: const int = 10; // Read-only (Type qualified)
// y = 20; // Error: cannot assign to const
Type Inference: Zen C automatically infers types for initialized variables. It compiles to C23
autoon supported compilers, or GCC's__auto_typeextension otherwise.
| Type | C Equivalent | Description |
|---|---|---|
int, uint |
int, unsigned int |
Platform standard integer |
I8 .. I128 or i8 .. i128 |
int8_t .. __int128_t |
Signed fixed-width integers |
U8 .. U128 or u8 .. u128 |
uint8_t .. __uint128_t |
Unsigned fixed-width integers |
isize, usize |
ptrdiff_t, size_t |
Pointer-sized integers |
byte |
uint8_t |
Alias for U8 |
F32, F64 or f32, f64 |
float, double |
Floating point numbers |
bool |
bool |
true or false |
char |
char |
Single character |
string |
char* |
C-string (null-terminated) |
U0, u0, void |
void |
Empty type |
iN (for example, i256) |
_BitInt(N) |
Arbitrary bit-width signed integer (C23) |
uN (for example, u42) |
unsigned _BitInt(N) |
Arbitrary bit-width unsigned integer (C23) |
Fixed-size arrays with value semantics.
def SIZE = 5;
let ints: int[SIZE] = [1, 2, 3, 4, 5];
let zeros: [int; SIZE]; // Zero-initialized
Group multiple values together, access elements by index.
let pair = (1, "Hello");
let x = pair.0; // 1
let s = pair.1; // "Hello"
Multiple Return Values
Functions can return tuples to provide multiple results:
fn add_and_subtract(a: int, b: int) -> (int, int) {
return (a + b, a - b);
}
let result = add_and_subtract(3, 2);
let sum = result.0; // 5
let diff = result.1; // 1
Destructuring
Tuples can be destructured directly into variables:
let (sum, diff) = add_and_subtract(3, 2);
// sum = 5, diff = 1
Data structures with optional bitfields.
struct Point {
x: int;
y: int;
}
// Struct initialization
let p = Point { x: 10, y: 20 };
// Bitfields
struct Flags {
valid: U8 : 1;
mode: U8 : 3;
}
Note: Structs use Move Semantics by default. Fields can be accessed via
.even on pointers (Auto-Dereference).
You can define a struct as opaque to restrict access to its fields to the defining module only, while still allowing the struct to be allocated on the stack (size is known).
// In user.zc
opaque struct User {
id: int;
name: string;
}
fn new_user(name: string) -> User {
return User{id: 1, name: name}; // OK: Inside module
}
// In main.zc
import "user.zc";
fn main() {
let u = new_user("Alice");
// let id = u.id; // Error: Cannot access private field 'id'
}
Tagged unions (Sum types) capable of holding data.
enum Shape {
Circle(float), // Holds radius
Rect(float, float), // Holds width, height
Point // No data
}
Standard C unions (unsafe access).
union Data {
i: int;
f: float;
}
Create a new name for an existing type.
alias ID = int;
alias PointMap = Map<string, Point>;
You can define a type alias as opaque to create a new type that is distinct from its underlying type outside of the defining module. This provides strong encapsulation and type safety without the runtime overhead of a wrapper struct.
// In library.zc
opaque alias Handle = int;
fn make_handle(v: int) -> Handle {
return v; // Implicit conversion allowed inside module
}
// In main.zc
import "library.zc";
fn main() {
let h: Handle = make_handle(42);
// let i: int = h; // Error: Type validation failed
// let h2: Handle = 10; // Error: Type validation failed
}
fn add(a: int, b: int) -> int {
return a + b;
}
// Named arguments supported in calls
add(a: 10, b: 20);
Note: Named arguments must strictly follow the defined parameter order.
add(b: 20, a: 10)is invalid.
Function arguments can be marked as const to enforce read-only semantics. This is a type qualifier, not a manifest constant.
fn print_val(v: const int) {
// v = 10; // Error: Cannot assign to const variable
println "{v}";
}
Functions can define default values for trailing arguments. These can be literals, expressions, or valid Zen C code (like struct constructors).
// Simple default value
fn increment(val: int, amount: int = 1) -> int {
return val + amount;
}
// Expression default value (evaluated at call site)
fn offset(val: int, pad: int = 10 * 2) -> int {
return val + pad;
}
// Struct default value
struct Config { debug: bool; }
fn init(cfg: Config = Config { debug: true }) {
if cfg.debug { println "Debug Mode"; }
}
fn main() {
increment(10); // 11
offset(5); // 25
init(); // Prints "Debug Mode"
}
Anonymous functions that can capture their environment.
let factor = 2;
let double = x -> x * factor; // Arrow syntax
let full = fn(x: int) -> int { return x * factor; }; // Block syntax
Zen C supports raw C function pointers using the fn* syntax. This allows seamless interop with C libraries that expect function pointers without closure overhead.
// Function taking a raw function pointer
fn set_callback(cb: fn*(int)) {
cb(42);
}
// Function returning a raw function pointer
fn get_callback() -> fn*(int) {
return my_handler;
}
// Pointers to function pointers are supported (fn**)
let pptr: fn**(int) = &ptr;
Functions can accept a variable number of arguments using ... and the va_list type.
fn log(lvl: int, fmt: char*, ...) {
let ap: va_list;
va_start(ap, fmt);
vprintf(fmt, ap); // Use C stdio
va_end(ap);
}
if x > 10 {
print("Large");
} else if x > 5 {
print("Medium");
} else {
print("Small");
}
// Ternary
let y = x > 10 ? 1 : 0;
Powerful alternative to switch.
match val {
1 => { print "One" },
2 || 3 => { print "Two or Three" }, // OR with ||
4 or 5 => { print "Four or Five" }, // OR with 'or'
6, 7, 8 => { print "Six to Eight" }, // OR with comma
10 .. 15 => { print "10 to 14" }, // Exclusive range (Legacy)
10 ..< 15 => { print "10 to 14" }, // Exclusive range (Explicit)
20 ..= 25 => { print "20 to 25" }, // Inclusive range
_ => { print "Other" },
}
// Destructuring Enums
match shape {
Shape::Circle(r) => println "Radius: {r}",
Shape::Rect(w, h) => println "Area: {w*h}",
Shape::Point => println "Point"
}
To inspect a value without taking ownership (moving it), use the ref keyword in the pattern. This is essential for types that implement Move Semantics (like Option, Result, non-Copy structs).
let opt = Some(NonCopyVal{...});
match opt {
Some(ref x) => {
// 'x' is a pointer to the value inside 'opt'
// 'opt' is NOT moved/consumed here
println "{x.field}";
},
None => {}
}
// Range
for i in 0..10 { ... } // Exclusive (0 to 9)
for i in 0..<10 { ... } // Exclusive (Explicit)
for i in 0..=10 { ... } // Inclusive (0 to 10)
for i in 0..10 step 2 { ... }
// Iterator (Vec or custom Iterable)
for item in vec { ... }
// Iterate over fixed-size arrays directly
let arr: int[5] = [1, 2, 3, 4, 5];
for val in arr {
// val is int
println "{val}";
}
// While
while x < 10 { ... }
// Infinite with label
outer: loop {
if done { break outer; }
}
// Repeat N times
for _ in 0..5 { ... }
// Guard: Execute else and return if condition is false
guard ptr != NULL else { return; }
// Unless: If not true
unless is_valid { return; }
Zen C supports operator overloading for user-defined structs by implementing specific method names.
| Category | Operator | Method Name |
|---|---|---|
| Arithmetic | +, -, *, /, % |
add, sub, mul, div, rem |
| Comparison | ==, != |
eq, neq |
<, >, <=, >= |
lt, gt, le, ge |
|
| Bitwise | &, |, ^ |
bitand, bitor, bitxor |
<<, >> |
shl, shr |
|
| Unary | - |
neg |
! |
not |
|
~ |
bitnot |
|
| Index | a[i] |
get(a, i) |
a[i] = v |
set(a, i, v) |
Note on String Equality:
string == stringperforms value comparison (equivalent tostrcmp).char* == char*performs pointer comparison (checks memory addresses).- Mixed comparisons (e.g.
string == char*) default to pointer comparison.
Example:
impl Point {
fn add(self, other: Point) -> Point {
return Point{x: self.x + other.x, y: self.y + other.y};
}
}
let p3 = p1 + p2; // Calls p1.add(p2)
These operators are built-in language features and cannot be overloaded directly.
| Operator | Name | Description |
|---|---|---|
|> |
Pipeline | x |> f(y) desugars to f(x, y) |
?? |
Null Coalescing | val ?? default returns default if val is NULL (pointers) |
??= |
Null Assignment | val ??= init assigns if val is NULL |
?. |
Safe Navigation | ptr?.field accesses field only if ptr is not NULL |
? |
Try Operator | res? returns error if present (Result/Option types) |
Auto-Dereference:
Pointer field access (ptr.field) and method calls (ptr.method()) automatically dereference the pointer, equivalent to (*ptr).field.
Zen C provides versatile options for printing to the console, including keywords and concise shorthands.
print "text": Prints tostdoutwithout a trailing newline.println "text": Prints tostdoutwith a trailing newline.eprint "text": Prints tostderrwithout a trailing newline.eprintln "text": Prints tostderrwith a trailing newline.
Zen C allows you to use string literals directly as statements for quick printing:
"Hello World": Equivalent toprintln "Hello World". (Implicitly adds newline)"Hello World"..: Equivalent toprint "Hello World". (No trailing newline)!"Error": Equivalent toeprintln "Error". (Output to stderr)!"Error"..: Equivalent toeprint "Error". (Output to stderr, no newline)
You can embed expressions directly into string literals using {} syntax. This works with all printing methods and string shorthands.
let x = 42;
let name = "Zen";
println "Value: {x}, Name: {name}";
"Value: {x}, Name: {name}"; // shorthand println
Zen C supports a shorthand for prompting user input using the ? prefix.
? "Prompt text": Prints the prompt (without newline) and waits for input (reads a line).? "Enter age: " (age): Prints prompt and scans input into the variableage.- Format specifiers are automatically inferred based on variable type.
let age: int;
? "How old are you? " (age);
println "You are {age} years old.";
Zen C allows manual memory management with ergonomic aids.
Execute code when the current scope exits. Defer statements are executed in LIFO (last-in, first-out) order.
let f = fopen("file.txt", "r");
defer fclose(f);
To prevent undefined behavior, control flow statements (
return,break,continue,goto) are not allowed inside adeferblock.
Automatically free the variable when scope exits.
autofree let types = malloc(1024);
Zen C treats types with destructors (like File, Vec, or malloc'd pointers) as Resources. To prevent double-free errors, resources cannot be implicitly duplicated.
- Move by Default: Assigning a resource variable transfers ownership. The original variable becomes invalid (Moved).
- Copy Types: Types without destructors may opt-in to
Copybehavior, making assignment a duplication.
Diagnostics & Philosophy: If you see an error "Use of moved value", the compiler is telling you: "This type owns a resource (like memory or a handle) and blindly copying it is unsafe."
Contrast: Unlike C/C++, Zen C does not implicitly duplicate resource-owning values.
Function Arguments: Passing a value to a function follows the same rules as assignment: resources are moved unless passed by reference.
fn process(r: Resource) { ... } // 'r' is moved into function
fn peek(r: Resource*) { ... } // 'r' is borrowed (reference)
Explicit Cloning: If you do want two copies of a resource, make it explicit:
let b = a.clone(); // Calls the 'clone' method from the Clone trait
Opt-in Copy (Value Types): For small types without destructors:
struct Point { x: int; y: int; }
impl Copy for Point {} // Opt-in to implicit duplication
fn main() {
let p1 = Point { x: 1, y: 2 };
let p2 = p1; // Copied. p1 stays valid.
}
Implement Drop to run cleanup logic automatically.
impl Drop for MyStruct {
fn drop(self) {
self.free();
}
}
Define methods on types using impl.
impl Point {
// Static method (constructor convention)
fn new(x: int, y: int) -> Self {
return Point{x: x, y: y};
}
// Instance method
fn dist(self) -> float {
return sqrt(self.x * self.x + self.y * self.y);
}
}
Define shared behavior.
struct Circle { radius: f32; }
trait Drawable {
fn draw(self);
}
impl Drawable for Circle {
fn draw(self) { ... }
}
let circle = Circle{};
let drawable: Drawable = &circle;
Zen C includes standard traits that integrate with language syntax.
Iterable
Implement Iterable<T> to enable for-in loops for your custom types.
import "std/iter.zc"
// Define an Iterator
struct MyIter {
curr: int;
stop: int;
}
impl MyIter {
fn next(self) -> Option<int> {
if self.curr < self.stop {
self.curr += 1;
return Option<int>::Some(self.curr - 1);
}
return Option<int>::None();
}
}
// Implement Iterable
impl MyRange {
fn iterator(self) -> MyIter {
return MyIter{curr: self.start, stop: self.end};
}
}
// Use in Loop
for i in my_range {
println "{i}";
}
Drop
Implement Drop to define a destructor that runs when the object goes out of scope (RAII).
import "std/mem.zc"
struct Resource {
ptr: void*;
}
impl Drop for Resource {
fn drop(self) {
if self.ptr != NULL {
free(self.ptr);
}
}
}
Note: If a variable is moved,
dropis NOT called on the original variable. It adheres to Resource Semantics.
Copy
Marker trait to opt-in to Copy behavior (implicit duplication) instead of Move semantics. Used via @derive(Copy).
Rule: Types that implement
Copymust not define a destructor (Drop).
@derive(Copy)
struct Point { x: int; y: int; }
fn main() {
let p1 = Point{x: 1, y: 2};
let p2 = p1; // Copied! p1 remains valid.
}
Clone
Implement Clone to allow explicit duplication of resource-owning types.
import "std/mem.zc"
struct MyBox { val: int; }
impl Clone for MyBox {
fn clone(self) -> MyBox {
return MyBox{val: self.val};
}
}
fn main() {
let b1 = MyBox{val: 42};
let b2 = b1.clone(); // Explicit copy
}
Use use to embed other structs. You can either mix them in (flatten fields) or name them (nest fields).
struct Entity { id: int; }
struct Player {
// Mixin (Unnamed): Flattens fields
use Entity; // Adds 'id' to Player directly
name: string;
}
struct Match {
// Composition (Named): Nests fields
use p1: Player; // Accessed via match.p1
use p2: Player; // Accessed via match.p2
}
Type-safe templates for Structs and Functions.
// Generic Struct
struct Box<T> {
item: T;
}
// Generic Function
fn identity<T>(val: T) -> T {
return val;
}
// Multi-parameter Generics
struct Pair<K, V> {
key: K;
value: V;
}
Built on pthreads.
async fn fetch_data() -> string {
// Runs in background
return "Data";
}
fn main() {
let future = fetch_data();
let result = await future;
}
Run code at compile-time to generate source or print messages.
comptime {
// Generate code at compile-time (written to stdout)
println "let build_date = \"2024-01-01\";";
}
println "Build Date: {build_date}";
Embed files as specified types.
// Default (Slice_char)
let data = embed "assets/logo.png";
// Typed Embed
let text = embed "shader.glsl" as string; // Embbed as C-string
let rom = embed "bios.bin" as u8[1024]; // Embed as fixed array
let wav = embed "sound.wav" as u8[]; // Embed as Slice_u8
Import compiler plugins to extend syntax.
import plugin "regex"
let re = regex! { ^[a-z]+$ };
Pass preprocessor macros through to C.
Tip: For simple constants, use
definstead. Use#definewhen you need C-preprocessor macros or conditional compilation flags.
#define MAX_BUFFER 1024
Decorate functions and structs to modify compiler behavior.
| Attribute | Scope | Description |
|---|---|---|
@must_use |
Fn | Warn if return value is ignored. |
@deprecated("msg") |
Fn/Struct | Warn on usage with message. |
@inline |
Fn | Hint compiler to inline. |
@noinline |
Fn | Prevent inlining. |
@packed |
Struct | Remove padding between fields. |
@align(N) |
Struct | Force alignment to N bytes. |
@constructor |
Fn | Run before main. |
@destructor |
Fn | Run after main exits. |
@unused |
Fn/Var | Suppress unused variable warnings. |
@weak |
Fn | Weak symbol linkage. |
@section("name") |
Fn | Place code in specific section. |
@noreturn |
Fn | Function does not return (e.g. exit). |
@pure |
Fn | Function has no side effects (optimization hint). |
@cold |
Fn | Function is unlikely to be executed (branch prediction hint). |
@hot |
Fn | Function is frequently executed (optimization hint). |
@export |
Fn/Struct | Export symbol (visibility default). |
@global |
Fn | CUDA: Kernel entry point (__global__). |
@device |
Fn | CUDA: Device function (__device__). |
@host |
Fn | CUDA: Host function (__host__). |
@comptime |
Fn | Helper function available for compile-time execution. |
@derive(...) |
Struct | Auto-implement traits. Supports Debug, Eq (Smart Derive), Copy, Clone. |
@ctype("type") |
Fn Param | Overrides generated C type for a parameter. |
@<custom> |
Any | Passes generic attributes to C (e.g. @flatten, @alias("name")). |
Zen C supports a powerful Custom Attribute system that allows you to use any GCC/Clang __attribute__ directly in your code. Any attribute that is not explicitly recognized by the Zen C compiler is treated as a generic attribute and passed through to the generated C code.
This provides access to advanced compiler features, optimizations, and linker directives without needing explicit support in the language core.
Zen C attributes are mapped directly to C attributes:
@name→__attribute__((name))@name(args)→__attribute__((name(args)))@name("string")→__attribute__((name("string")))
Zen C provides "Smart Derives" that respect Move Semantics:
@derive(Eq): Generates an equality method that takes arguments by reference (fn eq(self, other: T*)).- When comparing two non-Copy structs (
a == b), the compiler automatically passesbby reference (&b) to avoid moving it. - Recursive equality checks on fields also prefer pointer access to prevent ownership transfer.
- When comparing two non-Copy structs (
Zen C provides first-class support for inline assembly, transpiling directly to GCC-style extended asm.
Write raw assembly within asm blocks. Strings are concatenated automatically.
asm {
"nop"
"mfence"
}
Prevent the compiler from optimizing away assembly that has side effects.
asm volatile {
"rdtsc"
}
Zen C simplifies the complex GCC constraint syntax with named bindings.
// Syntax: : out(variable) : in(variable) : clobber(reg)
// Uses {variable} placeholder syntax for readability
fn add(a: int, b: int) -> int {
let result: int;
asm {
"add {result}, {a}, {b}"
: out(result)
: in(a), in(b)
: clobber("cc")
}
return result;
}
| Type | Syntax | GCC Equivalent |
|---|---|---|
| Output | : out(variable) |
"=r"(variable) |
| Input | : in(variable) |
"r"(variable) |
| Clobber | : clobber("rax") |
"rax" |
| Memory | : clobber("memory") |
"memory" |
Note: When using Intel syntax (via
-masm=intel), you must ensure your build is configured correctly (for example,//> cflags: -masm=intel). TCC does not support Intel syntax assembly.
Zen C supports special comments at the top of your source file to configure the build process without needing a complex build system or Makefile.
| Directive | Arguments | Description |
|---|---|---|
//> link: |
-lfoo or path/to/lib.a |
Link against a library or object file. |
//> lib: |
path/to/libs |
Add a library search path (-L). |
//> include: |
path/to/headers |
Add an include search path (-I). |
//> framework: |
Cocoa |
Link against a macOS framework. |
//> cflags: |
-Wall -O3 |
Pass arbitrary flags to the C compiler. |
//> define: |
MACRO or KEY=VAL |
Define a preprocessor macro (-D). |
//> pkg-config: |
gtk+-3.0 |
Run pkg-config and append --cflags and --libs. |
//> shell: |
command |
Execute a shell command during the build. |
//> get: |
http://url/file |
Download a file if specific file does not exist. |
1. OS Guarding
Prefix directives with an OS name to apply them only on specific platforms.
Supported prefixes: linux:, windows:, macos: (or darwin:).
//> linux: link: -lm
//> windows: link: -lws2_32
//> macos: framework: Cocoa
2. Environment Variable Expansion
Use ${VAR} syntax to expand environment variables in your directives.
//> include: ${HOME}/mylib/include
//> lib: ${ZC_ROOT}/std
//> include: ./include
//> lib: ./libs
//> link: -lraylib -lm
//> cflags: -Ofast
//> pkg-config: gtk+-3.0
import "raylib.h"
fn main() { ... }
The following keywords are reserved in Zen C.
alias, def, enum, fn, impl, import, let, module, opaque, struct, trait, union, use
async, await, break, catch, continue, defer, else, for, goto, guard, if, loop, match, return, try, unless, while
asm, assert, autofree, comptime, const, embed, launch, ref, sizeof, static, test, volatile
true, false, null
The following identifiers are reserved because they are keywords in C11:
auto, case, char, default, do, double, extern, float, inline, int, long, register, restrict, short, signed, switch, typedef, unsigned, void, _Atomic, _Bool, _Complex, _Generic, _Imaginary, _Noreturn, _Static_assert, _Thread_local
and, or
Zen C includes a standard library (std) covering essential functionality.
Browse the Standard Library Documentation
| Module | Description | Docs |
|---|---|---|
std/vec.zc |
Growable dynamic array Vec<T>. |
Docs |
std/string.zc |
Heap-allocated String type with UTF-8 support. |
Docs |
std/queue.zc |
FIFO queue (Ring Buffer). | Docs |
std/map.zc |
Generic Hash Map Map<V>. |
Docs |
std/fs.zc |
File system operations. | Docs |
std/io.zc |
Standard Input/Output (print/println). |
Docs |
std/option.zc |
Optional values (Some/None). |
Docs |
std/result.zc |
Error handling (Ok/Err). |
Docs |
std/path.zc |
Cross-platform path manipulation. | Docs |
std/env.zc |
Process environment variables. | Docs |
std/net.zc |
TCP networking (Sockets). | Docs |
std/thread.zc |
Threads and Synchronization. | Docs |
std/time.zc |
Time measurement and sleep. | Docs |
std/json.zc |
JSON parsing and serialization. | Docs |
std/stack.zc |
LIFO Stack Stack<T>. |
Docs |
std/set.zc |
Generic Hash Set Set<T>. |
Docs |
std/process.zc |
Process execution and management. | Docs |
Zen C provides a built-in Language Server and REPL to enhance the development experience.
The Zen C Language Server (LSP) supports standard LSP features for editor integration, providing:
- Go to Definition
- Find References
- Hover Information
- Completion (Function/Struct names, Dot-completion for methods/fields)
- Document Symbols (Outline)
- Signature Help
- Diagnostics (Syntax/Semantic errors)
To start the language server (typically configured in your editor's LSP settings):
zc lspIt communicates via standard I/O (JSON-RPC 2.0).
The Read-Eval-Print Loop allows you to experiment with Zen C code interactively.
zc repl- Interactive Coding: Type expressions or statements for immediate evaluation.
- Persistent History: Commands are saved to
~/.zprep_history. - Startup Script: Auto-loads commands from
~/.zprep_init.zc.
| Command | Description |
|---|---|
:help |
Show available commands. |
:reset |
Clear current session history (variables/functions). |
:vars |
Show active variables. |
:funcs |
Show user-defined functions. |
:structs |
Show user-defined structs. |
:imports |
Show active imports. |
:history |
Show session input history. |
:type <expr> |
Show the type of an expression. |
:c <stmt> |
Show the generated C code for a statement. |
:time <expr> |
Benchmark an expression (runs 1000 iterations). |
:edit [n] |
Edit command n (default: last) in $EDITOR. |
:save <file> |
Save the current session to a .zc file. |
:load <file> |
Load and execute a .zc file into the session. |
:watch <expr> |
Watch an expression (re-evaluated after every entry). |
:unwatch <n> |
Remove a watch. |
:undo |
Remove the last command from the session. |
:delete <n> |
Remove command at index n. |
:clear |
Clear the screen. |
:quit |
Exit the REPL. |
! <cmd> |
Run a shell command (e.g. !ls). |
Zen C is designed to work with most C11 compilers. Some features rely on GNU C extensions, but these often work in other compilers. Use the --cc flag to switch backends.
zc run app.zc --cc clang
zc run app.zc --cc zig| Compiler | Pass Rate | Supported Features | Known Limitations |
|---|---|---|---|
| GCC | 100% | All Features | None. |
| Clang | 100% | All Features | None. |
| Zig | 100% | All Features | None. Uses zig cc as a drop-in C compiler. |
| TCC | ~70% | Basic Syntax, Generics, Traits | No __auto_type, No Intel ASM, No Nested Functions. |
Recommendation: Use GCC, Clang, or Zig for production builds. TCC is excellent for rapid prototyping due to its compilation speed but misses some advanced C extensions Zen C relies on for full feature support.
Zig's zig cc command provides a drop-in replacement for GCC/Clang with excellent cross-compilation support. To use Zig:
# Compile and run a Zen C program with Zig
zc run app.zc --cc zig
# Build the Zen C compiler itself with Zig
make zigZen C can generate C++-compatible code with the --cpp flag, allowing seamless integration with C++ libraries.
# Direct compilation with g++
zc app.zc --cpp
# Or transpile for manual build
zc transpile app.zc --cpp
g++ out.c my_cpp_lib.o -o appInclude C++ headers and use raw blocks for C++ code:
include <vector>
include <iostream>
raw {
std::vector<int> make_vec(int a, int b) {
return {a, b};
}
}
fn main() {
let v = make_vec(1, 2);
raw { std::cout << "Size: " << v.size() << std::endl; }
}
Note: The
--cppflag switches the backend tog++and emits C++-compatible code (usesautoinstead of__auto_type, function overloads instead of_Generic, and explicit casts forvoid*).
Zen C supports GPU programming by transpiling to CUDA C++. This allows you to leverage powerful C++ features (templates, constexpr) within your kernels while maintaining Zen C's ergonomic syntax.
# Direct compilation with nvcc
zc run app.zc --cuda
# Or transpile for manual build
zc transpile app.zc --cuda -o app.cu
nvcc app.cu -o app| Attribute | CUDA Equivalent | Description |
|---|---|---|
@global |
__global__ |
Kernel function (runs on GPU, called from host) |
@device |
__device__ |
Device function (runs on GPU, called from GPU) |
@host |
__host__ |
Host function (explicit CPU-only) |
Zen C provides a clean launch statement for invoking CUDA kernels:
launch kernel_name(args) with {
grid: num_blocks,
block: threads_per_block,
shared_mem: 1024, // Optional
stream: my_stream // Optional
};
This transpiles to: kernel_name<<<grid, block, shared, stream>>>(args);
Use Zen C function syntax with @global and the launch statement:
import "std/cuda.zc"
@global
fn add_kernel(a: float*, b: float*, c: float*, n: int) {
let i = thread_id();
if i < n {
c[i] = a[i] + b[i];
}
}
fn main() {
def N = 1024;
let d_a = cuda_alloc<float>(N);
let d_b = cuda_alloc<float>(N);
let d_c = cuda_alloc<float>(N);
defer cuda_free(d_a);
defer cuda_free(d_b);
defer cuda_free(d_c);
// ... init data ...
launch add_kernel(d_a, d_b, d_c, N) with {
grid: (N + 255) / 256,
block: 256
};
cuda_sync();
}
Zen C provides a standard library for common CUDA operations to reduce raw blocks:
import "std/cuda.zc"
// Memory management
let d_ptr = cuda_alloc<float>(1024);
cuda_copy_to_device(d_ptr, h_ptr, 1024 * sizeof(float));
defer cuda_free(d_ptr);
// Synchronization
cuda_sync();
// Thread Indexing (use inside kernels)
let i = thread_id(); // Global index
let bid = block_id();
let tid = local_id();
Note: The
--cudaflag setsnvccas the compiler and implies--cppmode. Requires the NVIDIA CUDA Toolkit.
Zen C supports modern C23 features when using a compatible backend compiler (GCC 14+, Clang 14+, TCC (partial)).
auto: Zen C automatically maps type inference to standard C23autoif__STDC_VERSION__ >= 202300L._BitInt(N): UseiNanduNtypes (e.g.,i256,u12,i24) to access C23 arbitrary-width integers.
Zen C can compile to Objective-C (.m) using the --objc flag, allowing you to use Objective-C frameworks (like Cocoa/Foundation) and syntax.
# Compile with clang (or gcc/gnustep)
zc app.zc --objc --cc clangUse include for headers and raw blocks for Objective-C syntax (@interface, [...], @"").
//> macos: framework: Foundation
//> linux: cflags: -fconstant-string-class=NSConstantString -D_NATIVE_OBJC_EXCEPTIONS
//> linux: link: -lgnustep-base -lobjc
include <Foundation/Foundation.h>
fn main() {
raw {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSLog(@"Hello from Objective-C!");
[pool drain];
}
println "Zen C works too!";
}
Note: Zen C string interpolation works with Objective-C objects (
id) by callingdebugDescriptionordescription.
We welcome contributions! Whether it's fixing bugs, adding documentation, or proposing new features.
- Fork the Repository: standard GitHub workflow.
- Create a Feature Branch:
git checkout -b feature/NewThing. - Code Guidelines:
- Follow the existing C style.
- Ensure all tests pass:
make test. - Add new tests for your feature in
tests/.
- Submit a Pull Request: Describe your changes clearly.
The test suite is your best friend.
# Run all tests (GCC)
make test
# Run specific test
./zc run tests/test_match.zc
# Run with different compiler
./tests/run_tests.sh --cc clang
./tests/run_tests.sh --cc zig
./tests/run_tests.sh --cc tcc- Parser:
src/parser/- Recursive descent parser. - Codegen:
src/codegen/- Transpiler logic (Zen C -> GNU C/C11). - Standard Library:
std/- Written in Zen C itself.
This project uses third-party libraries. Full license texts can be found in the LICENSES/ directory.
- cJSON (MIT License): Used for JSON parsing and generation in the Language Server.
- zc-ape (MIT License): The original Actually Portable Executable port of Zen-C by Eugene Olonov.
- Cosmopolitan Libc (ISC License): The foundational library that makes APE possible.