Source

Zen C

Modern Ergonomics. Zero Overhead. Pure C.

Write like a high-level language, run like C.

Overview

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.

Quick Start

Installation

git clone https://github.com/z-libs/Zen-C.git
cd Zen-C
make
sudo make install

Portable Build (APE)

Zen 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:
- cosmocc toolchain (must be in your PATH)

Build & Install:

make ape
sudo env "PATH=$PATH" make install-ape

Artifacts:
- 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

Usage

# Compile and run
zc run hello.zc

# Build executable
zc build hello.zc -o hello

# Interactive Shell
zc repl

Environment Variables

You can set ZC_ROOT to specify the location of the Standard Library (standard imports like import "std/vector.zc"). This allows you to run zc from any directory.

export ZC_ROOT=/path/to/Zen-C

Language Reference

1. Variables and Constants

Zen C distinguishes between compile-time constants and runtime variables.

Manifest Constants (def)

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

Variables (let)

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

2. Primitive Types

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

3. Aggregate Types

Arrays

Fixed-size arrays with value semantics.

def SIZE = 5;
let ints: int[SIZE] = {1, 2, 3, 4, 5};
let zeros: [int; SIZE]; // Zero-initialized

Tuples

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

Structs

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).

Enums

Tagged unions (Sum types) capable of holding data.

enum Shape {
    Circle(float),      // Holds radius
    Rect(float, float), // Holds width, height
    Point               // No data
}

Unions

Standard C unions (unsafe access).

union Data {
    i: int;
    f: float;
}

Type Aliases

Create a new name for an existing type.

alias ID = int;
alias PointMap = Map<string, Point>;

4. Functions & Lambdas

Functions

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.

Const Arguments

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}";
}

Default Arguments

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"
}

Lambdas (Closures)

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

Raw Function Pointers

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;

Variadic Functions

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);
}

5. Control Flow

Conditionals

if x > 10 {
    print("Large");
} else if x > 5 {
    print("Medium");
} else {
    print("Small");
}

// Ternary
let y = x > 10 ? 1 : 0;

Pattern Matching

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"
}

Reference Binding

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 => {}
}

Loops

// 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, Array, or custom Iterable)
for item in collection { ... }

// While
while x < 10 { ... }

// Infinite with label
outer: loop {
    if done { break outer; }
}

// Repeat N times
for _ in 0..5 { ... }

Advanced Control

// Guard: Execute else and return if condition is false
guard ptr != NULL else { return; }

// Unless: If not true
unless is_valid { return; }

6. Operators

Zen C supports operator overloading for user-defined structs by implementing specific method names.

Overloadable Operators

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)

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)

Syntactic Sugar

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.

7. Printing and String Interpolation

Zen C provides versatile options for printing to the console, including keywords and concise shorthands.

Keywords

Shorthands

Zen C allows you to use string literals directly as statements for quick printing:

String Interpolation (F-strings)

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

Input Prompts (?)

Zen C supports a shorthand for prompting user input using the ? prefix.

let age: int;
? "How old are you? " (age);
println "You are {age} years old.";

8. Memory Management

Zen C allows manual memory management with ergonomic aids.

Defer

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 a defer block.

Autofree

Automatically free the variable when scope exits.

autofree let types = malloc(1024);

Resource Semantics (Move by Default)

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.

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.
}

RAII / Drop Trait

Implement Drop to run cleanup logic automatically.

impl Drop for MyStruct {
    fn drop(self) {
        self.free();
    }
}

9. Object Oriented Programming

Methods

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);
    }
}

Traits

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;

Standard Traits

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, drop is 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 Copy must 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
}

Composition

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
}

10. Generics

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;
}

11. Concurrency (Async/Await)

Built on pthreads.

async fn fetch_data() -> string {
    // Runs in background
    return "Data";
}

fn main() {
    let future = fetch_data();
    let result = await future;
}

12. Metaprogramming

Comptime

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

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

Plugins

Import compiler plugins to extend syntax.

import plugin "regex"
let re = regex! { ^[a-z]+$ };

Generic C Macros

Pass preprocessor macros through to C.

Tip: For simple constants, use def instead. Use #define when you need C-preprocessor macros or conditional compilation flags.

#define MAX_BUFFER 1024

13. Attributes

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).
@derive(...) Struct Auto-implement traits. Supports Debug, Eq (Smart Derive), Copy, Clone.

Smart Derives

Zen C provides "Smart Derives" that respect Move Semantics:

14. Inline Assembly

Zen C provides first-class support for inline assembly, transpiling directly to GCC-style extended asm.

Basic Usage

Write raw assembly within asm blocks. Strings are concatenated automatically.

asm {
    "nop"
    "mfence"
}

Volatile

Prevent the compiler from optimizing away assembly that has side effects.

asm volatile {
    "rdtsc"
}

Named Constraints

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.

15. Build Directives

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).
//> 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.

Examples

//> include: ./include
//> lib: ./libs
//> link: -lraylib -lm
//> cflags: -Ofast
//> pkg-config: gtk+-3.0

import "raylib.h"

fn main() { ... }

Standard Library

Zen C includes a standard library (std) covering essential functionality.

Browse the Standard Library Documentation

Key Modules

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

Tooling

Zen C provides a built-in Language Server and REPL to enhance the development experience.

Language Server (LSP)

The Zen C Language Server (LSP) supports standard LSP features for editor integration, providing:

To start the language server (typically configured in your editor's LSP settings):

zc lsp

It communicates via standard I/O (JSON-RPC 2.0).

REPL

The Read-Eval-Print Loop allows you to experiment with Zen C code interactively.

zc repl

Features

Commands

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).

Compiler Support & Compatibility

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

Test Suite Status

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.

Building with Zig

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 zig

C++ Interop

Zen 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 app

Using C++ in Zen C

Include 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 --cpp flag switches the backend to g++ and emits C++-compatible code (uses auto instead of __auto_type, function overloads instead of _Generic, and explicit casts for void*).

CUDA Interop

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

CUDA-Specific Attributes

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)

Kernel Launch Syntax

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);

Writing CUDA Kernels

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() {
    const 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();
}

Standard Library (std/cuda.zc)

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 --cuda flag sets nvcc as the compiler and implies --cpp mode. Requires the NVIDIA CUDA Toolkit.


Running Tests

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

Extending the Compiler


Tags: language  

Last modified 17 February 2026