Zen C 是一种现代系统编程语言,可编译为人类可读的 GNU C/C11。它提供了一套丰富的特性,包括类型推断、模式匹配、泛型、Trait、async/await 以及具有 RAII 能力的手动内存管理,同时保持 100% 的 C ABI 兼容性。
加入官方 Zen C Discord 服务器,参与讨论、展示 Demo、提问或报告 Bug!
Zen C 项目包含多个仓库。下面是主要的仓库列表:
| 仓库 | 描述 | 状态 |
|---|---|---|
| zenc | Zen C 核心编译器 (zc)、CLI 和标准库。 |
活跃开发 |
| docs | 官方技术文档与语言规范。 | 活跃 |
| rfcs | 征求意见稿 (RFC) 仓库。塑造语言的未来。 | 活跃 |
| vscode-zenc | 官方 VS Code 扩展(语法高亮、代码片段)。 | Alpha |
| www | zenc-lang.org 的源代码。 |
活跃 |
| awesome-zenc | 精选的 Zen C 示例列表。 | 不断增加 |
| zenc.vim | 官方 Vim/Neovim 插件(语法高亮、智能缩进)。 | 活跃 |
查看这些使用 Zen C 构建的项目:
- ZC-pong-3ds: Nintendo 3DS 上的 Pong 克隆版。
- zen-c-parin: 使用 Parin 的 Zen C 基础示例。
- almond: 用 Zen C 编写的极简网页浏览器。
| 通用 | 语言参考 |
|---|---|
git clone https://github.com/zenc-lang/zenc.git
cd Zen-C
make clean # 移除旧的构建文件
make
sudo make installZen C 对 Windows (x86_64) 提供完备的原生支持。你可以使用提供的批处理脚本配合 GCC (MinGW) 进行构建:
build.bat这将构建编译器 (zc.exe)。网络、文件系统和进程操作通过平台抽象层 (PAL) 得到完全支持。
或者,如果你有类 Unix 环境(MSYS2、Cygwin、git-bash),也可以使用 make。
Zen C 可以通过 Cosmopolitan Libc 编译为 Actually Portable Executable (APE)。这将生成一个单个的可执行文件 (.com),能够原生运行在 Linux, macOS, Windows, FreeBSD, OpenBSD, 和 NetBSD 上的 x86_64 和 aarch64 架构上。
前提条件:
cosmocc工具链(必须在 PATH 中)
构建与安装:
make ape
sudo env "PATH=$PATH" make install-ape产物:
out/bin/zc.com: 便携式 Zen-C 编译器。已将标准库嵌入到可执行文件中。out/bin/zc-boot.com: 一个自包含的引导安装程序,用于设置新的 Zen-C 项目。
用法:
# 在任何支持的操作系统上运行
./out/bin/zc.com build hello.zc -o hello# 编译并运行
zc run hello.zc
# 构建可执行文件
zc build hello.zc -o hello
# 交互式 Shell
zc repl
# 显示 Zen Facts
zc build hello.zc --zen你可以设置 ZC_ROOT 来指定标准库的位置(标准导入如 import "std/vector.zc")。这允许你从任何目录运行 zc。
export ZC_ROOT=/path/to/Zen-CZen C 区分编译时常量和运行时变量。
仅在编译时存在的值(折叠到代码中)。用于数组大小、固定配置和魔术数字。
def MAX_SIZE = 1024;
let buffer: char[MAX_SIZE]; // 有效的数组大小
内存中的存储位置。可以是可变的或只读的 (const)。
let x = 10; // 可变
x = 20; // 允许
let y: const int = 10; // 只读 (类型修饰)
// y = 20; // 错误:无法赋值给 const 变量
Tip
类型推导:Zen C 自动推导初始化变量的类型。在支持的编译器上编译为 C23 的 auto,否则使用 GCC 的 __auto_type 扩展。
| 类型 | C 等效类型 | 描述 |
|---|---|---|
int, uint |
int32_t, uint32_t |
32位有符号/无符号整数 |
c_char, c_uchar |
char, unsigned char |
C char (互操作) |
c_short, c_ushort |
short, unsigned short |
C short (互操作) |
c_int, c_uint |
int, unsigned int |
C int (互操作) |
c_long, c_ulong |
long, unsigned long |
C long (互操作) |
c_long_long, c_ulong_long |
long long, unsigned long long |
C long long / unsigned long long (互操作) |
I8 .. I128 或 i8 .. i128 |
int8_t .. __int128_t |
有符号固定宽度整数 |
U8 .. U128 或 u8 .. u128 |
uint8_t .. __uint128_t |
无符号固定宽度整数 |
isize, usize |
ptrdiff_t, size_t |
指针大小的整数 |
byte |
uint8_t |
U8 的别名 |
F32, F64 或 f32, f64 |
float, double |
浮点数 |
bool |
bool |
true 或 false |
char |
char |
单个字符 |
string |
char* |
C-string (以 null 结尾) |
U0, u0, void |
void |
空类型 |
iN (例 i256) |
_BitInt(N) |
任意位宽有符号整数 (C23) |
uN (例 u42) |
unsigned _BitInt(N) |
任意位宽无符号整数 (C23) |
- 整数: 十进制 (
123), 十六进制 (0xFF), 八进制 (0o755), 二进制 (0b1011).- 注意: 带有前导零的数字被视为十进制(
0123即123),这与 C 语言不同。 - 注意: 数字可以包含下划线以提高可读性 (
1_000_000,0b_1111_0000).
- 注意: 带有前导零的数字被视为十进制(
- 浮点数: 标准 (
3.14), 科学计数法 (1e-5,1.2E3). 浮点数也支持下划线 (3_14.15_92).
Important
可移植代码最佳实践
- 对于所有纯 Zen C 逻辑,请使用 可移植类型 (
int、uint、i64、u8等)。int保证在所有架构上都是 32 位有符号整数。 - 仅在与 C 库 (FFI) 交互时使用 C 互操作类型 (
c_int、c_char、c_long,c_ulong,c_long_long,c_ulong_long)。它们的大小因平台和 C 编译器而异。 - 使用
isize和usize进行数组索引和内存指针运算。
具有值语义的固定大小数组。
def SIZE = 5;
let ints: int[SIZE] = [1, 2, 3, 4, 5];
let zeros: [int; SIZE]; // 零初始化的
将多个值组合在一起,通过索引访问元素。
let pair = (1, "Hello");
let x = pair.0; // 1
let s = pair.1; // "Hello"
多个返回值
函数可以返回元组以提供多个结果:
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
解构
元组可以直接解构为多个变量:
let (sum, diff) = add_and_subtract(3, 2);
// sum = 5, diff = 1
带类型的解构允许显式类型注解:
let (a: string, b: u8) = ("hello", 42);
let (x, y: i32) = (1, 2); // 混合:x 推断,y 显式
带有可选位域的数据结构。
struct Point {
x: int;
y: int;
}
// 结构体初始化
let p = Point { x: 10, y: 20 };
// 位域
struct Flags {
valid: U8 : 1;
mode: U8 : 3;
}
Note
结构体默认使用 移动语义。即使是指针,也可以通过 . 访问字段(自动解引用)。
你可以将结构体定义为 opaque,以将对其字段的访问限制在定义该结构体的模块内部,同时仍允许在栈上分配该结构体(大小已知)。
// 在 user.zc 中
opaque struct User {
id: int;
name: string;
}
fn new_user(name: string) -> User {
return User{id: 1, name: name}; // 允许:在模块内部
}
// 在 main.zc 中
import "user.zc";
fn main() {
let u = new_user("Alice");
// let id = u.id; // 错误:无法访问私有字段 'id'
}
能够持有数据的标签联合 (Sum types)。
enum Shape {
Circle(float), // 持有半径
Rect(float, float), // 持有宽、高
Point // 不带数据
}
标准 C 联合体(不安全访问)。
union Data {
i: int;
f: float;
}
使用 GCC/Clang 向量扩展的原生 SIMD 向量类型。使用 @vector(N) 注解一个结构体来定义包含 N 个元素的向量。
import "std/simd.zc";
fn main() {
let a = f32x4{v: 1.0}; // 广播: {1.0, 1.0, 1.0, 1.0}
let b = f32x4{1.0, 2.0, 3.0, 4.0}; // 逐元素初始化
let c = a + b; // 逐元素加法
let x = c[0]; // 元素访问 (float)
}
算术运算符(+, -, *, /)和位运算符(&, |, ^)逐元素运算。预定义类型请参阅 std/simd.zc。
为现有类型创建新名称。
alias ID = int;
alias PointMap = Map<string, Point>
alias OpFunc = fn(int, int) -> int
注意: 类型别名末尾的分号是可选的。
你可以将类型别名定义为 opaque,从而在定义模块之外创建一个与基础类型不同的新类型。这提供了强大的封装和类型安全性,而没有包装结构体的运行时开销。
// 在 library.zc 中
opaque alias Handle = int;
fn make_handle(v: int) -> Handle {
return v; // 允许在模块内部进行隐式转换
}
// 在 main.zc 中
import "library.zc";
fn main() {
let h: Handle = make_handle(42);
// let i: int = h; // 错误:类型验证失败
// let h2: Handle = 10; // 错误:类型验证失败
}
fn add(a: int, b: int) -> int {
return a + b;
}
// 调用时支持命名参数
add(a: 10, b: 20);
Note
命名参数必须严格遵循定义的参数顺序。add(b: 20, a: 10) 是无效的。
函数参数可以标记为 const 以强制执行只读语义。这是一个类型修饰符,而不是清单常量。
fn print_val(v: const int) {
// v = 10; // 错误:无法赋值给 const 变量
println "{v}";
}
函数可以为尾部参数定义默认值。这些值可以是字面量、表达式或有效的 Zen C 代码(如结构体构造函数)。
// 简单默认值
fn increment(val: int, amount: int = 1) -> int {
return val + amount;
}
// 表达式默认值(在调用处计算)
fn offset(val: int, pad: int = 10 * 2) -> int {
return val + pad;
}
// 结构体默认值
struct Config { debug: bool; }
fn init(cfg: Config = Config { debug: true }) {
if cfg.debug { println "调试模式"; }
}
fn main() {
increment(10); // 11
offset(5); // 25
init(); // 打印 "调试模式"
}
可以捕获环境的匿名函数。
let factor = 2;
let doubler = x -> x * factor; // 箭头语法
let full = fn(x: int) -> int { return x * factor; }; // 块语法
// 引用捕获(块语法)
let val = 10;
let modify = fn[&]() { val += 1; };
modify(); // val 现在是 11
// 引用捕获(箭头语法)
let modify_arrow = [&] x -> val += x;
modify_arrow(5); // val 现在是 16
// 引用捕获(多参数箭头语法)
let sum_into = [&] (a, b) -> val += (a + b);
sum_into(2, 2); // val 现在是 20
// 值捕获(默认)
let original = 100;
let implicit = x -> original + x; // 隐式值捕获(无括号)
let explicit = [=] x -> original + x; // 显式值捕获
// let fail = x -> original += x; // 错误:无法赋值给捕获的值
Zen C 使用 fn* 语法支持原始 C 函数指针。这允许与期望函数指针且没有闭包开销的 C 库进行无缝互操作。
// 接受原始函数指针的函数
fn set_callback(cb: fn*(int)) {
cb(42);
}
// 返回原始函数指针的函数
fn get_callback() -> fn*(int) {
return my_handler;
}
// 支持指向函数指针的指针 (fn**)
let pptr: fn**(int) = &ptr;
函数可以使用 ... 和 va_list 类型接受可变数量的参数。
fn log(lvl: int, fmt: char*, ...) {
let ap: va_list;
va_start(ap, fmt);
vprintf(fmt, ap); // 使用 C stdio
va_end(ap);
}
if x > 10 {
print("Large");
} else if x > 5 {
print("Medium");
} else {
print("Small");
}
// 三元运算符
let y = x > 10 ? 1 : 0;
// If-表达式 (用于复杂条件)
let 类别 = if (x > 100) { "巨大" } else if (x > 10) { "大" } else { "小" };
switch 的强大替代方案。
match val {
1 => { print "One" },
2 || 3 => { print "Two or Three" }, // 使用 || 进行 或 操作
4 or 5 => { print "Four or Five" }, // 使用 'or' 进行 或 操作
6, 7, 8 => { print "Six to Eight" }, // 使用逗号进行 或 操作
10 .. 15 => { print "10 to 14" }, // 左闭右开区间 (旧语法)
10 ..< 15 => { print "10 to 14" }, // 左闭右开区间 (显式)
20 ..= 25 => { print "20 to 25" }, // 全闭区间
_ => { print "Other" },
}
// 解构枚举
match shape {
Shape::Circle(r) => { println "半径: {r}" },
Shape::Rect(w, h) => { println "面积: {w*h}" },
Shape::Point => { println "点" },
}
为了在不获取所有权(移动)的情况下检查一个值,在模式中使用 ref 关键字。这对于实现了移动语义的类型(如 Option, Result, 非 Copy 结构体)至关重要。
let opt = Some(NonCopyVal{...});
match opt {
Some(ref x) => {
// 'x' 是指向 'opt' 内部值的指针
// 'opt' 在此处不会被移动/消耗
println "{x.field}";
},
None => {}
}
// 區間迭代
for i in 0..10 { ... } // 左閉右開 (0 到 9)
for i in 0..<10 { ... } // 左閉右開 (顯式)
for i in 0..=10 { ... } // 全閉 (0 到 10)
for i in 0..10 step 2 { ... }
for i in 10..0 step -1 { ... } // Descending loop
// 迭代器 (Vec 或自定義 Iterable)
for item in vec { ... }
// 枚举:获取索引和值
for i, val in arr { ... } // i = 0, 1, 2, ...
for i, val in 0..10 step 2 { ... } // i = 0, 1, 2, ...; val = 0, 2, 4, ...
// 直接迭代固定大小数组
let arr: int[5] = [1, 2, 3, 4, 5];
for val in arr {
// val 是 int
println "{val}";
}
// While 循環
while x < 10 { ... }
// 帶標籤的無限循環
outer: loop {
if done { break outer; }
}
// 重複 N 次
for _ in 0..5 { ... }
// Guard: 如果条件为假,则执行 else 块并返回
guard ptr != NULL else { return; }
// Unless: 除非为真(即如果为假)
unless is_valid { return; }
Zen C 通过实现特定的方法名来支持用户定义结构体的运算符重载。
| 类别 | 运算符 | 方法名 |
|---|---|---|
| 算术 | +, -, *, /, %, ** |
add, sub, mul, div, rem, pow |
| 比较 | ==, != |
eq, neq |
<, >, <=, >= |
lt, gt, le, ge |
|
| 位运算 | &, ` |
, ^` |
<<, >> |
shl, shr |
|
| 一元 | - |
neg |
! |
not |
|
~ |
bitnot |
|
| 索引 | a[i] |
get(a, i) |
a[i, j] |
get(a, i, j) |
|
a[i] = v |
set(a, i, v) |
Note
关于字符串相等性的说明:
string == string进行 值比较(等同于strcmp)。char* == char*进行 指针比较(检查内存地址)。- 混合比较(例如
string == char*)默认为 指针比较。
示例:
impl Point {
fn add(self, other: Point) -> Point {
return Point{x: self.x + other.x, y: self.y + other.y};
}
}
let p3 = p1 + p2; // 调用 p1.add(p2)
多索引示例:
struct Matrix {
data: int[9];
}
impl Matrix {
fn get(self, row: int, col: int) -> int {
return self.data[row * 3 + col];
}
}
let m = Matrix{data: [1,0,0, 0,1,0, 0,0,1]};
let val = m[1, 2]; // 调用 Matrix.get(m, 1, 2)
这些运算符是内置语言特性,不能直接重载。
| 运算符 | 名称 | 描述 |
|---|---|---|
| ` | >` | 管道 |
?? |
空合并 | 如果 val 为 NULL,val ?? default 返回 default (用于指针) |
??= |
空赋值 | 如果 val 为 NULL 则赋值 |
?. |
安全导航 | 仅当 ptr 不为 NULL 时访问字段 |
? |
Try 运算符 | 如果存在错误则返回 (用于 Result/Option 类型) |
自动解引用:
指针字段访问 (ptr.field) 和方法调用 (ptr.method()) 会自动解引用指针,等同于 (*ptr).field。
Zen C 提供了多种控制台打印选项,包括关键字和简洁的简写形式。
| 关键字 | 描述 |
|---|---|
print "text" |
打印到 stdout,不带尾随换行符。 |
println "text" |
打印到 stdout,带尾随换行符。 |
eprint "text" |
打印到 stderr,不带尾随换行符。 |
eprintln "text" |
打印到 stderr,带尾随换行符。 |
Zen C 允许直接将字符串字面量用作语句来进行快速打印:
| 语法 | 等效项 | 描述 |
|---|---|---|
"Hello World" |
println "Hello World" |
隐式添加换行符。 |
"Hello World".. |
print "Hello World" |
不带尾随换行符。 |
!"Error" |
eprintln "Error" |
输出到 stderr。 |
!"Error".. |
eprint "Error" |
输出到 stderr,不带换行符。 |
你可以使用 {} 语法将表达式直接嵌入到字符串字面量中。这适用于所有打印方法和字符串简写。
Zen C 中的字符串插值是隐式的:如果你的字符串包含 {...},它将自动被解析为插值字符串。你也可以显式地使用 f 作为前缀(例如 f"..."),以强制执行插值语义。
let x = 42;
let name = "Zen";
println "值: {x}, 名称: {name}";
"值: {x}, 名称: {name}"; // 简写形式的 println
转义花括号:使用 {{ 输出字面量 {,使用 }} 输出字面量 }:
let json = "JSON: {{\"键\": \"值\"}}";
// 输出: JSON: {"键": "值"}
原始字符串 (Raw Strings):若要定义完全忽略插值与转义序列的字符串,请在其前加上 r(例如 r"..."):
let regex = r"\w+"; // 包含精确的 \ w +
let raw_json = r'{"key": "value"}'; // 不需要转义大括号
Zen C 使用 """ 分隔符支持原始多行字符串块。这对于编写嵌入式语言(如 GLSL、HTML)或在 comptime 块生成 C 代码非常有用,此时无需手动转义换行符与内部引号。
与标准字符串一样,多行字符串支持隐式插值。您也可以显式地加上前缀:
f"""...""": 显式标记为插值字符串块。r"""...""": 显式标记为原始字符串块(无插值,无转义序列)。
let prompt = """
请输入您的名字:
输入 "exit" 取消。
""";
let world = "world";
let script = """
fn hello() {
println "hello, {world}!";
}
""";
let pure_raw = r"""
这里的 {braces} 只是纯文本,而 \n 就只是斜线和 n。
""";
Zen C 支持使用 ? 前缀进行用户输入提示的简写。
? "提示文本": 打印提示信息(不换行)并等待输入(读取一行)。? "输入年龄: " (age): 打印提示并扫描输入到变量age中。- 格式说明符会根据变量类型自动推断。
let age: int;
? "你多大了? " (age);
println "你 {age} 岁了。";
Zen C 允许带有符合人体工程学辅助的手动内存管理。
在当前作用域退出时执行代码。Defer 语句按照后进先出 (LIFO) 的顺序执行。
let f = fopen("file.txt", "r");
defer fclose(f);
Warning
为了防止未定义行为,defer 块内不允许使用控制流语句(return, break, continue, goto)。
在作用域退出时自动释放变量。
autofree let types = malloc(1024);
Zen C 将带有析构函数(如 File, Vec, 或 malloc 的指针)的类型视为 资源。为了防止双重释放错误,资源不能被隐式复制。
- 默认移动:分配资源变量会转移所有权。原始变量变得无效(已移动)。
- 复制类型:没有析构函数的类型可以申请参与
Copy行为,使赋值变成复制。
诊断与哲学: 如果你看到错误 "Use of moved value",编译器是在告诉你:"此类型拥有一个资源(如内存或句柄),盲目复制它是不安全的。"
Note
对比: 与 C/C++ 不同,Zen C 不会隐式复制拥有资源的值。
函数参数: 将值传递给函数遵循与赋值相同的规则:资源会被移动,除非通过引用传递。
fn process(r: Resource) { ... } // 'r' 被移动进函数
fn peek(r: Resource*) { ... } // 'r' 被借用 (引用)
显式克隆: 如果你 确实 想要一个资源的两个副本,请显式执行:
let b = a.clone(); // 调用 Clone trait 中的 'clone' 方法
选择性复制 (值类型): 对于没有析构函数的小型类型:
struct Point { x: int; y: int; }
impl Copy for Point {} // 选择参与隐式复制
fn main() {
let p1 = Point { x: 1, y: 2 };
let p2 = p1; // 已复制。p1 保持有效。
}
实现 Drop 以自动运行清理逻辑。
impl Drop for MyStruct {
fn drop(self) {
self.free();
}
}
使用 impl 为类型定义方法。
impl Point {
// 静态方法 (构造函数惯例)
fn new(x: int, y: int) -> Self {
return Point{x: x, y: y};
}
// 实例方法
fn dist(self) -> float {
return sqrt(self.x * self.x + self.y * self.y);
}
}
Self 简写: 在具有 self 参数的方法中,可以使用 .字段 作为 self.字段 的简写:
impl Point {
fn dist(self) -> float {
return sqrt(.x * .x + .y * .y); // 等同于 self.x, self.y
}
}
Zen C 允许你使用相同的 impl 语法定义原始类型(如 int, bool 等)的方法。
impl int {
fn abs(self) -> int {
return *self < 0 ? -(*self) : *self;
}
}
let x = -10;
let y = x.abs(); // 10
let z = (-5).abs(); // 5 (Literals supported)
定义共享行为。
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 包含与语言语法集成的标准 Trait。
Iterable
实现 Iterable<T> 以便为你的自定义类型启用 for-in 循环。
import "std/iter.zc"
// 定义一个迭代器
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();
}
}
// 实现 Iterable
impl MyRange {
fn iterator(self) -> MyIter {
return MyIter{curr: self.start, stop: self.end};
}
}
// 在循环中使用
for i in my_range {
println "{i}";
}
Drop
实现 Drop 来定义一个在对象超出范围时运行的析构函数 (RAII)。
import "std/mem.zc"
struct Resource {
ptr: void*;
}
impl Drop for Resource {
fn drop(self) {
if self.ptr != NULL {
free(self.ptr);
}
}
}
Note
如果一个变量被移动,则原始变量不会调用 drop。它遵循 资源语义。
Copy
标记 Trait,用于选择支持 Copy 行为(隐式复制)而不是移动语义。通过 @derive(Copy) 使用。
Caution
实现了 Copy 的类型不得定义析构函数 (Drop)。
@derive(Copy)
struct Point { x: int; y: int; }
fn main() {
let p1 = Point{x: 1, y: 2};
let p2 = p1; // 已复制!p1 保持有效。
}
Clone
实现 Clone 以允许显式复制拥有资源的类型。
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(); // 显式复制
}
使用 use 嵌入其他结构体。你可以将它们混合进来(展平字段)或者为它们命名(嵌套字段)。
struct Entity { id: int; }
struct Player {
// 混入 (未命名): 展平字段
use Entity; // 直接将 'id' 添加到 Player
name: string;
}
struct Match {
// 组合 (命名): 嵌套字段
use p1: Player; // 通过 match.p1 访问
use p2: Player; // 通过 match.p2 访问
}
结构体和函数的类型安全模板。
// 泛型结构体
struct Box<T> {
item: T;
}
// 泛型函数
fn identity<T>(val: T) -> T {
return val;
}
// 多参数泛型
struct Pair<K, V> {
key: K;
value: V;
}
基于 pthreads 构建。
async fn fetch_data() -> string {
// 在后台运行
return "Data";
}
fn main() {
let future = fetch_data();
let result = await future;
}
在编译时运行代码以生成源代码或打印消息。
comptime {
// 在编译时生成代码(写入 stdout)
println "let build_date = \"2024-01-01\";";
}
println "Build Date: {build_date}";
辅助函数
comptime 块内可用的特殊函数:
yield(str)- 显式输出生成的代码(printf 的替代方案)compile_error(msg)- 以致命错误消息停止编译compile_warn(msg)- 发出编译时警告(允许继续编译)
comptime {
compile_warn("正在生成优化代码...");
let ENABLE_FEATURE = 1;
if (ENABLE_FEATURE == 0) {
compile_error("必须启用功能!");
}
println "let FEATURE_ENABLED = 1;";
}
构建元数据
在编译时访问编译器构建信息:
__COMPTIME_TARGET__- 平台字符串:"linux","windows"或"macos"__COMPTIME_FILE__- 当前正在编译的源文件名
comptime {
// 平台特定的代码生成
println "let PLATFORM = \"{__COMPTIME_TARGET__}\";";
}
println "运行于: {PLATFORM}";
Tip
在 comptime 字符串内使用 {{ 和 }} 来转义花括号。
将文件嵌入为指定类型。
// 默认 (Slice_char)
let data = embed "assets/logo.png";
// 类型化嵌入
let text = embed "shader.glsl" as string; // 嵌入为 C-string
let rom = embed "bios.bin" as u8[1024]; // 嵌入为固定数组
let wav = embed "sound.wav" as u8[]; // 嵌入为 Slice_u8
导入编译器插件以扩展语法。
import plugin "regex"
let re = regex! { ^[a-z]+$ };
将预处理器宏传递给 C。
Tip
对于简单的常量,请使用 def。当你需要 C 预处理器宏或条件编译标志时,请使用 #define。
#define MAX_BUFFER 1024
使用 @cfg() 根据 -D 标志有条件地包含或排除任何顶层声明。
// 编译: zc build app.zc -DUSE_OPENGL
@cfg(USE_OPENGL)
import "opengl_backend.zc";
@cfg(USE_VULKAN)
import "vulkan_backend.zc";
@cfg(not(USE_OPENGL))
@cfg(not(USE_VULKAN))
fn fallback_init() { println "未选择后端"; }
| 形式 | 含义 |
|---|---|
@cfg(NAME) |
如果设置了 -DNAME 则包含 |
@cfg(not(NAME)) |
如果未设置 -DNAME 则包含 |
@cfg(any(A, B, ...)) |
如果任意条件为真则包含 (OR) |
@cfg(all(A, B, ...)) |
如果所有条件为真则包含 (AND) |
一个声明上的多个 @cfg 使用 AND 组合。not() 可以在 any() 和 all() 内部使用。适用于任何顶层声明:fn、struct、import、impl、raw、def、test 等。
修饰函数和结构体以修改编译器行为。
| 属性 | 作用域 | 描述 |
|---|---|---|
@must_use |
函数 | 如果忽略返回值则发出警告。 |
@deprecated("msg") |
函数/结构体 | 使用时发出带有消息的警告。 |
@inline |
函数 | 提示编译器进行内联。 |
@noinline |
函数 | 防止内联。 |
@packed |
结构体 | 移除字段间的填充。 |
@align(N) |
结构体 | 强制按 N 字节对齐。 |
@constructor |
函数 | 在 main 之前运行。 |
@destructor |
函数 | 在 main 退出后运行。 |
@unused |
函数/变量 | 抑制未使用变量警告。 |
@weak |
函数 | 弱符号链接。 |
@section("name") |
函数 | 将代码放置在特定段中。 |
@noreturn |
函数 | 函数不会返回 (例如 exit)。 |
@pure |
函数 | 函数无副作用 (优化提示)。 |
@cold |
函数 | 函数不太可能被执行 (分支预测提示)。 |
@hot |
函数 | 函数频繁执行 (优化提示)。 |
@export |
函数/结构体 | 导出符号 (默认可见性)。 |
@global |
函数 | CUDA: 内核入口点 (__global__)。 |
@device |
函数 | CUDA: 设备函数 (__device__)。 |
@host |
函数 | CUDA: 主机函数 (__host__)。 |
@comptime |
函数 | 用于编译时执行的辅助函数。 |
@cfg(NAME) |
任意 | 条件编译:仅在传递 -DNAME 时包含。支持 not()、any()、all()。 |
@derive(...) |
结构体 | 自动实现 Trait。支持 Debug, Eq (智能派生), Copy, Clone。 |
@ctype("type") |
函数参数 | 覆盖参数生成的 C 类型。 |
@<custom> |
任意 | 将泛型属性传递给 C (例如 @flatten, @alias("name"))。 |
Zen C 支持强大的 自定义属性 系统,允许你在代码中直接使用任何 GCC/Clang 的 __attribute__。任何不被 Zen C 编译器显式识别的属性都会被视为泛型属性并传递给生成的 C 代码。
这提供了对高级编译器特性、优化和链接器指令的访问,而无需在语言核心中提供显式支持。
Zen C 属性直接映射到 C 属性:
@name→__attribute__((name))@name(args)→__attribute__((name(args)))@name("string")→__attribute__((name("string")))
Zen C 提供了尊重移动语义的 "智能派生":
@derive(Eq):生成一个通过引用获取参数的相等性方法 (fn eq(self, other: T*))。- 当比较两个非 Copy 结构体 (
a == b) 时,编译器会自动通过引用传递b(&b) 以避免移动它。 - 字段上的递归相等性检查也会优先使用指针访问,以防止所有权转移。
- 当比较两个非 Copy 结构体 (
Zen C 为内联汇编提供了一流支持,直接转译为 GCC 风格的扩展 asm。
在 asm 块内编写原始汇编。字符串会自动拼接。
asm {
"nop"
"mfence"
}
防止编译器优化掉具有副作用的汇编代码。
asm volatile {
"rdtsc"
}
Zen C 通过命名绑定简化了复杂的 GCC 约束语法。
// 语法: : out(变量) : in(变量) : clobber(寄存器)
// 使用 {变量} 占位符语法以提高可读性
fn add_five(x: int) -> int {
let result: int;
asm {
"mov {x}, {result}"
"add $5, {result}"
: out(result)
: in(x)
: clobber("cc")
}
return result;
}
| 类型 | 语法 | GCC 等效项 |
|---|---|---|
| 输出 | : out(variable) |
"=r"(variable) |
| 输入 | : in(variable) |
"r"(variable) |
| 破坏 | : clobber("rax") |
"rax" |
| 内存 | : clobber("memory") |
"memory" |
Note
使用 Intel 语法时(通过 -masm=intel),必须确保你的构建配置正确(例如,//> cflags: -masm=intel)。TCC 不支持 Intel 语法的汇编。
Zen C 支持在源文件顶部使用特殊注释来配置构建过程,无需复杂的构建系统或 Makefile。
| 指令 | 参数 | 描述 |
|---|---|---|
//> link: |
-lfoo 或 path/to/lib.a |
链接库或对象文件。 |
//> lib: |
path/to/libs |
添加库搜索路径 (-L)。 |
//> include: |
path/to/headers |
添加包含头文件搜索路径 (-I)。 |
//> framework: |
Cocoa |
链接 macOS Framework。 |
//> cflags: |
-Wall -O3 |
向 C 编译器传递任意标志。 |
//> define: |
MACRO 或 KEY=VAL |
定义预处理器宏 (-D)。 |
//> pkg-config: |
gtk+-3.0 |
运行 pkg-config 并追加 --cflags 和 --libs。 |
//> shell: |
command |
在构建期间执行 shell 命令。 |
//> get: |
http://url/file |
如果特定文件不存在,则下载该文件。 |
1. 操作系统守护 (OS Guarding)
在指令前加上操作系统名称,以使其仅在特定平台上应用。
受支持的前缀:linux:, windows:, macos: (或 darwin:)。
//> linux: link: -lm
//> windows: link: -lws2_32
//> macos: framework: Cocoa
2. 环境变量展开
使用 ${VAR} 语法在指令中展开环境变量。
//> 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() { ... }
以下关键字在 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
以下标识符是保留的,因为它们是 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 提供了两种与 C 代码交互的方式:信任导入 (Trusted Imports) (方便) 和 显式 FFI (安全/精确)。
你可以使用 import 关键字直接导入 .h 扩展名的 C 头文件。这会将头文件视为一个模块,并假设通过它访问的所有符号都存在。
//> link: -lm
import "math.h" as c_math;
fn main() {
// 编译器信任不仅正确;直接生成 'cos(...)'
let x = c_math::cos(3.14159);
}
Note
零样板代码。立即访问头文件中的所有内容。 缺点: Zen C 不提供类型安全 (错误将在稍后由 C 编译器捕获)。
对于严格的类型检查,或当你不想包含头文件文本时,请使用 extern fn.
include <stdio.h> // 在生成的 C 代码中发出 #include <stdio.h>
// 定义严格签名
extern fn printf(fmt: char*, ...) -> c_int;
fn main() {
printf("Hello FFI: %d\n", 42); // 由 Zen C 进行类型检查
}
Note
Zen C 确保类型匹配。 缺点: 需要手动声明函数。
import "file.h": 将头文件注册为命名模块。启用对符号的隐式访问 (例如file::function())。include <file.h>: 纯粹在生成的 C 代码中发出#include <file.h>。不向 Zen C 编译器引入任何符号;必须使用extern fn才能访问它们。
Zen C 包含一个涵盖基本功能的标准库 (std)。
点击查看所有标准库模块
| 模块 | 描述 | 文档 |
|---|---|---|
std/bigfloat.zc |
任意精度浮点运算。 | 文档 |
std/bigint.zc |
任意精度整数 BigInt。 |
文档 |
std/bits.zc |
底层位运算操作 (rotl, rotr 等)。 |
文档 |
std/complex.zc |
复数算术 Complex。 |
文档 |
std/vec.zc |
可增长动态数组 Vec<T>。 |
文档 |
std/string.zc |
堆分配的 String 类型,支持 UTF-8。 |
文档 |
std/queue.zc |
先进先出队列 (环形缓冲区)。 | 文档 |
std/map.zc |
泛型哈希表 Map<V>。 |
文档 |
std/fs.zc |
文件系统操作。 | 文档 |
std/io.zc |
标准输入/输出 (print/println)。 |
文档 |
std/option.zc |
可选值 (Some/None)。 |
文档 |
std/result.zc |
错误处理 (Ok/Err)。 |
文档 |
std/path.zc |
跨平台路径操作。 | 文档 |
std/env.zc |
进程环境变量。 | 文档 |
std/net/ |
TCP, UDP, HTTP, DNS, URL. | 文档 |
std/thread.zc |
线程与同步。 | 文档 |
std/time.zc |
时间测量与睡眠。 | 文档 |
std/json.zc |
JSON 解析与序列化。 | 文档 |
std/stack.zc |
后进先出栈 Stack<T>。 |
文档 |
std/set.zc |
泛型哈希集合 Set<T>。 |
文档 |
std/process.zc |
进程执行与管理。 | 文档 |
std/regex.zc |
正则表达式 (基于 TRE)。 | 文档 |
std/simd.zc |
原生 SIMD 向量类型。 | 文档 |
Zen C 提供内置的语言服务器 (LSP) 和 REPL 以增强开发体验。
Zen C 语言服务器 (LSP) 支持标准的 LSP 特性,用于编辑器集成:
- 转到定义
- 查找引用
- 悬停信息
- 补全 (函数/结构体名,方法/字段的点补全)
- 文档符号 (大纲)
- 签名帮助
- 诊断 (语法/语义错误)
启动语言服务器(通常在编辑器的 LSP 设置中配置):
zc lsp它通过标准 I/O (JSON-RPC 2.0) 进行通信。
Read-Eval-Print Loop 允许你交互式地尝试 Zen C 代码。
zc repl- 交互式编码:输入表达式或语句以立即求值。
- 持久历史:命令保存在
~/.zprep_history中。 - 启动脚本:自动加载
~/.zprep_init.zc中的命令。
| 命令 | 描述 |
|---|---|
:help |
显示可用命令。 |
:reset |
清除当前会话历史 (变量/函数)。 |
:vars |
显示活跃变量。 |
:funcs |
显示用户定义的函数。 |
:structs |
显示用户定义的结构体。 |
:imports |
显示活跃导入。 |
:history |
显示会话输入历史。 |
:type <expr> |
显示表达式的类型。 |
:c <stmt> |
显示语句生成的 C 代码。 |
:time <expr> |
基准测试表达式 (运行 1000 次迭代)。 |
:edit [n] |
在 $EDITOR 中编辑命令 n (默认:最后一条)。 |
:save <file> |
将当前会话保存到 .zc 文件。 |
:load <file> |
将 .zc 文件加载并执行到会话中。 |
:watch <expr> |
监视表达式 (每次输入后重新求值)。 |
:unwatch <n> |
移除监视。 |
:undo |
从会话中移除最后一条命令。 |
:delete <n> |
移除索引为 n 的命令。 |
:clear |
清屏。 |
:quit |
退出 REPL。 |
! <cmd> |
运行 shell 命令 (如 !ls)。 |
Zen C 包含一个内置的语言服务器,用于编辑器集成。
- 安装与设置指南
- 支持的编辑器: VS Code, Neovim, Vim, Zed, 以及任何支持 LSP 的编辑器。
使用 zc lsp 启动服务器。
Zen C 程序可以使用标准的 C 调试器(如 LLDB 或 GDB)进行调试。
为了在 VS Code 中获得最佳体验,请安装官方的 Zen C 扩展。对于调试,您可以使用 C/C++(由 Microsoft 提供)或 CodeLLDB 扩展。
将这些配置添加到您的 .vscode 目录中,以启用一键调试:
tasks.json (构建任务):
{
"label": "Zen C: Build Debug",
"type": "shell",
"command": "zc",
"args": [ "${file}", "-g", "-o", "${fileDirname}/app", "-O0" ],
"group": { "kind": "build", "isDefault": true }
}launch.json (调试器):
{
"name": "Zen C: Debug (LLDB)",
"type": "lldb",
"request": "launch",
"program": "${fileDirname}/app",
"preLaunchTask": "Zen C: Build Debug"
}Zen C 旨在与大多数 C11 编译器配合使用。某些特性依赖于 GNU C 扩展,但这些扩展通常在其他编译器中也能工作。使用 --cc 标志切换后端。
zc run app.zc --cc clang
zc run app.zc --cc zig点击查看编译器支持详情
| 编译器 | 通过率 | 受支持特性 | 已知局限性 |
|---|---|---|---|
| GCC | 100% (全面) | 所有特性 | 无. |
| Clang | 100% (全面) | 所有特性 | 无. |
| Zig | 100% (全面) | 所有特性 | 无. 使用 zig cc 作为替代 C 编译器. |
| TCC | 98% (高) | 结构体, 泛型, Trait, 模式匹配 | 不支持 Intel ASM, 不支持 __attribute__((constructor)). |
Warning
编译器构建警告: 虽然 Zig CC 作为 Zen C 程序的后端非常出色,但使用它构建 Zen C 编译器本身可能会通过验证,但会生成无法通过测试的不稳定二进制文件。我们建议使用 GCC 或 Clang 构建编译器,并仅将 Zig 用作操作代码的后端。
Zig 的 zig cc 命令提供了 GCC/Clang 的替代方案,具有出色的跨平台编译支持。使用 Zig:
# 使用 Zig 编译并运行 Zen C 程序
zc run app.zc --cc zig
# 使用 Zig 构建 Zen C 编译器本身
make zigZen C 可以通过 --cpp 标志生成 C++ 兼容的代码,从而实现与 C++ 库的无缝集成。
# 直接使用 g++ 编译
zc app.zc --cpp
# 或者转译用于手动构建
zc transpile app.zc --cpp
g++ out.c my_cpp_lib.o -o app包含 C++ 头文件并在 raw 块中使用 C++ 代码:
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
--cpp 标志会将后端切换为 g++ 并发出 C++ 兼容的代码(使用 auto 代替 __auto_type,使用函数重载代替 _Generic,以及对 void* 进行显式转换)。
Zen C 通过转译为 CUDA C++ 来支持 GPU 编程。这使你在维持 Zen C 人体工程学语法的同时,能够利用内核中的强大 C++ 特性(模板、constexpr)。
# 直接使用 nvcc 编译
zc run app.zc --cuda
# 或者转译用于手动构建
zc transpile app.zc --cuda -o app.cu
nvcc app.cu -o app| 属性 | CUDA 等效项 | 描述 |
|---|---|---|
@global |
__global__ |
内核函数 (运行在 GPU,从主机调用) |
@device |
__device__ |
设备函数 (运行在 GPU,从 GPU 调用) |
@host |
__host__ |
主机函数 (明确仅 CPU 运行) |
Zen C 提供了一个简洁的 launch 语句用于调用 CUDA 内核:
launch kernel_name(args) with {
grid: num_blocks,
block: threads_per_block,
shared_mem: 1024, // 可选
stream: my_stream // 可选
};
这转译为:kernel_name<<<grid, block, shared, stream>>>(args);
使用带有 @global 的 Zen C 函数语法和 launch 语句:
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);
// ... 初始化数据 ...
launch add_kernel(d_a, d_b, d_c, N) with {
grid: (N + 255) / 256,
block: 256
};
cuda_sync();
}
Zen C 为常见的 CUDA 操作提供了一个标准库,以减少 raw 块的使用:
import "std/cuda.zc"
// 内存管理
let d_ptr = cuda_alloc<float>(1024);
cuda_copy_to_device(d_ptr, h_ptr, 1024 * sizeof(float));
defer cuda_free(d_ptr);
// 同步
cuda_sync();
// 线程索引 (在内核内部使用)
let i = thread_id(); // 全局索引
let bid = block_id();
let tid = local_id();
Note
注意: --cuda 标志设置 nvcc 为编译器并隐含 --cpp 模式。需要安装 NVIDIA CUDA Toolkit。
当使用兼容的后端编译器(GCC 14+, Clang 14+)时,Zen C 支持现代 C23特性。
auto: 如果__STDC_VERSION__ >= 202300L,Zen C 会自动将类型推导映射到标准 C23auto。_BitInt(N): 使用iN和uN类型(例如i256,u12,i24)访问 C23 任意位宽整数。
Zen C 可以通过 --objc 标志编译为 Objective-C (.m),允许你使用 Objective-C 框架(如 Cocoa/Foundation)和语法。
# 使用 clang (或 gcc/gnustep) 编译
zc app.zc --objc --cc clang使用 include 包含头文件,并在 raw 块中使用 Objective-C 语法 (@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(@"来自 Objective-C 的问候!");
[pool drain];
}
println "Zen C 也能正常工作!";
}
Note
注意: Zen C 字符串插值通过调用 debugDescription 或 description 同样适用于 Objective-C 对象 (id)。
我们欢迎各类贡献!无论是修复 Bug、完善文档,还是提出新功能建议。
请参阅 CONTRIBUTING_ZH_CN.md 了解有关如何贡献、运行测试和提交拉取请求的详细指南。
关于安全漏洞报告的说明,请参阅 SECURITY_ZH_CN.md。
本项目使用了第三方库。完整许可证文本可在 LICENSES/ 目录中找到。
- cJSON (MIT 许可证):用于语言服务器中的 JSON 解析和生成。
- zc-ape (MIT 许可证):由 Eugene Olonov 开发的原版 Zen-C 实际上便携的可执行文件 (APE) 端口。
- Cosmopolitan Libc (ISC 许可证):使 APE 成为可能的基础库。
- TRE (BSD 许可证): 用于标准库中的正则表达式引擎。
- zenc.vim (MIT 许可证): 官方 Vim/Neovim 插件,主要作者为 davidscholberg。