Go派生语言-Solod
Solod: Go can be a better CSolod(So)是Go的一个严格子集,可转换为常规C语言(翻译为可读的 C11 代码)。主要采用Go编写开发并在BSD 3类协议下授权使用。

So supports structs, methods, interfaces, slices, maps, multiple returns, and defer. Everything is stack-allocated by default; heap is opt-in through the standard library. To keep things simple, there are no channels, goroutines, closures, or generics.
特性
Go in, C out. You write regular Go code and get readable C11 as output.
Zero runtime. No garbage collection, no reference counting, no hidden allocations.
Rich standard library. Use familiar types and functions ported from Go's stdlib.
Native C interop. Call C from So and So from C — no CGO, no overhead.
Go tooling works out of the box. Syntax highlighting, LSP, linting and "go test".
Solod 适用于那些希望获得系统级控制能力而无需学习新语言的 Go 开发者,同时也适用于那些喜欢 Go 的安全性、结构和工具链的 C 程序员。
Go 的官方 FAQ 开篇称:"Go 是 C 家族的成员"。Rob Pike 和 Ken Thompson 设计 Go 的初衷,确实是做一个"更好的 C"——解决 C 的内存安全和编译速度问题,同时保持系统编程的能力。但 17 年过去了,Go 走向了不同的方向:垃圾回收器越来越精密,runtime 越来越庞大,goroutine 调度器越来越复杂。Go 是优秀的云原生语言,但它离"更好的 C"越来越远。
TinyGo 试图在嵌入式场景恢复 Go 的轻量性,但仍然带着 GC 和 runtime。Zig 宣称是"更好的 C",但语法完全不同,学习成本不低。
作者 Anton Zhiyanov 的回答是 Solod:保留 Go 的语法和类型系统,但把 GC、runtime、goroutine 全部砍掉,直接翻译为 C。会 Go 就会 Solod。
Rob Pike 当年说 Go 是"更好的 C",但 GC、goroutine、runtime 一个也没落下。Anton Zhiyanov(redka 4.5K Star、sqlean 作者)用 381 个 commit 做了一个更激进的尝试:Solod(So),Go 的严格子集,直接翻译为 C11——零运行时、零 GC、手动内存管理、源码级 C 互操作。复用 go/ast 和 go/packages 做 AST 分析,约 20 个文件实现完整的 C 代码生成器。接口翻译为 {void* + 函数指针},defer 编译时内联展开,切片翻译为 {ptr, len, cap}。Go 标准库(strings、io、bytes、os 等)正在被逐个移植为纯 C 实现。
截止2026年一季度其代码构成为:Go 95%(1MB)+ C 4.5%(48KB)。以下部分引用自Go语言中文网。
三个核心决策
1、严格子集:所有 Solod 代码都是合法的 Go 代码(语法层面),但去掉了 goroutines、channels、closures、完整泛型等需要运行时支持的特性;
2、零运行时:没有 GC、没有调度器、没有反射、没有 RTTI。生成纯 C11 代码;
3、源码级 C 互操作:不是 FFI、不是 CGO——直接在 Go 代码中 #include C 头文件,调用 C 函数。
编译器架构:复用 Go 工具链
编译管线
Go 源码 (.go)
↓ go/packages 加载(含完整类型信息)
Go AST + 类型信息
↓ 拓扑排序依赖图
按依赖顺序逐包翻译
↓ clang 包(~20 个文件)遍历 AST
C11 源码(.h + .c 文件对)
↓ CC 编译器(GCC/Clang/zig cc)
可执行文件
其设计为:不做自己的词法分析、语法解析和类型检查。完全复用 Go 的 go/packages 和 go/ast。编译器只需要做一件事——AST 到 C 的代码生成。这意味着 Solod 享受 Go 工具链的所有好处:go vet 检查、gopls 补全、go fmt 格式化。用普通的 Go IDE 写 Solod 代码,IDE 不知道它不是"真正的 Go"。
internal/clang/ 包约 20 个文件,职责清晰:
| 文件 | 职责 |
| visit.go | AST 访问者模式,遍历所有 Go AST 节点 |
| types.go | Go 类型到 C 类型的映射 |
| expr.go | 表达式翻译 |
| function.go | 函数声明翻译 |
| struct.go | 结构体翻译 |
| interface.go | 接口翻译(最复杂) |
| string.go | 字符串操作翻译 |
| assign.go | 赋值语句翻译 |
| switch.go | switch 语句(翻译为 if/else 链) |
| range.go | for-range 循环翻译 |
| result.go | 多返回值(翻译为结构体) |
| symbols.go | 符号收集与管理 |
| header.go | .h 文件生成 |
每个 Go 包翻译为一个 .h + .c 文件对。导出符号加包名前缀(如 geom_RectArea),未导出符号标记为 static。
CLI 入口
so translate # 翻译为 C
so build # 翻译 + 编译
so run # 翻译 + 编译 + 运行
极简的三步工作流和 go run 一样简单。
类型映射:Go 到 C
基本类型
| Go 类型 | C 类型 | 说明 |
| int | so_int (int64_t) | 64 位整数 |
| float64 | double | 双精度浮点 |
| byte | uint8_t | 字节 |
| rune | int32_t | Unicode 码点 |
| bool | bool | C11 布尔 |
| string | so_String {ptr, len} | 非零终止,携带长度 |
复合类型
切片翻译为:
typedef struct so_Slice {
void* ptr;
so_int len;
so_int cap;
} so_Slice;
和 Go 的切片头完全同构——{指针, 长度, 容量}。
Map翻译为固定大小的栈分配 hash 表(mask-step-index 算法),不支持动态扩容——这是"零运行时"的代价。
多返回值翻译为结构体:
func divmod(a, b int) (int, int) {
return a / b, a % b
}
typedef struct divmod_result { so_int _0; so_int _1; } divmod_result;
divmod_result divmod(so_int a, so_int b) {
return (divmod_result){a / b, a % b};
}
接口翻译:{void* + 函数指针}
Go 的接口在运行时需要类型信息(interface word pair),Solod 没有运行时,所以用最直接的方式:
type Shape interface {
Area() int
Perim(n int) int
}
翻译为:
typedef struct main_Shape {
void* self; // 数据指针
so_int (*Area)(void* self); // 方法 1
so_int (*Perim)(void* self, so_int n); // 方法 2
} main_Shape;
每个接口值 = {void* 数据指针 + 一组函数指针},接口的隐式转换在翻译时插入:
r := Rect{width: 10, height: 5}
calcShape(&r) // 隐式转换 Rect → Shape
main_Rect r = (main_Rect){.width = 10, .height = 5};
calcShape((main_Shape){
.self = &r,
.Area = main_Rect_Area,
.Perim = main_Rect_Perim
});
这就是 Go 接口的本质——没有魔法,就是 {数据 + 行为} 的组合。Solod 把这一点用 C 代码清晰地展示了出来。
不支持:类型断言和 type switch(需要 RTTI,与零运行时冲突了)。
defer:编译时内联展开
Go 的 defer 在运行时通过 defer 链表实现,因为 Solod 没有运行时,所以 defer 在编译时内联展开(LIFO 顺序):
func funcScope() {
xopen(&state)
defer xclose(&state)
if state != 1 {
panic("unexpected state")
}
}
生成的 C:
static void funcScope(void) {
xopen(&state);
if (state != 1) {
xclose(&state); // defer 在 panic 前执行
so_panic("unexpected state");
}
xclose(&state); // defer 在函数结尾执行
}
语义差异:Solod 的 defer 是块作用域(block-scoped),Go 的 defer 是函数作用域(function-scoped)。这意味着同一函数内多个 defer 块的执行顺序与 Go 不同,这是少数 Solod 代码行为与 Go 不一致的地方。
内存管理:栈优先,显式堆
核心原则
语言内建操作不允许堆分配。
| 操作 | 实现 |
| make([]T, n) | alloca() 栈分配 |
| new(T) | 复合字面量取地址 &(T){0} |
| append() | 仅在初始容量内工作,超出 panic |
| Map | 固定大小栈分配 hash 表 |
这意味着 Solod 代码的内存布局完全可控——你明确知道每个分配发生在栈上还是堆上。
Allocator 接口
受 Zig 和 Odin 启发,Solod 提供了显式的分配器接口:
type Allocator interface {
Alloc(size int, align int) (any, error)
Realloc(ptr any, oldSize int, newSize int, align int) (any, error)
Free(ptr any, size int, align int)
}
内置实现:
| 分配器 | 策略 | 适用场景 |
| SystemAllocator | calloc/realloc/free | 通用场景 |
| Arena | Bump 分配器,Reset 一次释放全部 | 临时计算、请求处理 |
| Tracker | 包装任意分配器,检测内存泄漏 | 调试 |
高层 API(so/mem 包):
ptr := mem.Alloc[int](allocator) // 分配单个值
mem.Free(allocator, ptr)
s := mem.AllocSlice[int](allocator, 10, 20) // 分配切片
mem.FreeSlice(allocator, s)
Zig 的影子:显式分配器作为参数传递的模式,让调用者控制内存策略——这是 Zig 的核心理念,但用 Go 语法表达。
C 互操作:零开销的双向桥梁
Go 调 C
//so:include <stdio.h>
//so:extern FILE
type os_file struct{}
//so:extern
func fopen(path string, mode string) *os_file { return nil }
f := fopen("/tmp/test.txt", "w")
翻译为:
#include <stdio.h>
os_file* f = fopen("/tmp/test.txt", "w");
自动类型衰减:string 自动变为 char*,切片自动变为裸指针。不是 CGO 的运行时转换——是编译时的源码级映射,零开销。
C 调 Go(Solod)
Solod 编译为纯 C,所以 C 调用 Solod 函数就是调用 C 函数——带包名前缀的普通 C 函数。
辅助工具(so/c 包)
c.String(ptr) // char* → so_String
c.Bytes(ptr, n) // void* → so_Slice
c.CharPtr(ptr) // *byte → char*
c.Sizeof[T]() // 类型大小
c.Alloca(n) // 栈分配
Go 标准库的 C 移植
Anton 正在将 Go 标准库的核心包逐个移植为纯 C 实现。这是 Solod 最有野心的部分——不仅仅是编译器,还要建立一套与 Go API 兼容的 C 标准库。
| 包 | 说明 |
| strings | Builder、Reader、Compare、Split 等 |
| bytes | Buffer、Reader、Compare、Contains |
| io | Reader/Writer/Closer 接口,Copy,ReadAll |
| os | 文件操作,基于 POSIX API |
| fmt | 使用 C 的 printf/scanf |
| math | 与 Go math 包相同 API |
| strconv | 数字字符串转换 |
| time | UTC 时间 |
| unicode/utf8 | UTF-8 编解码 |
| maps | 泛型 HashMap(Robin Hood) |
| slices | 堆分配动态切片 |
| mem | 分配器接口 + 实现 |
移植策略:API 尽量与 Go 标准库保持一致,但实现为纯 C,可独立于 Go 使用。这意味着这些 C 实现不仅服务于 Solod——任何 C 项目都可以用。
Anton 还在博客上记录了移植过程:《Porting Go's strings package to C》、《Porting Go's io package to C》。这些文章本身也是 Go 标准库设计分析的佳作。
性能基准测试
Apple M1 + Go 1.26.1,Solod 使用 mimalloc,编译选项 -Ofast -march=native -flto -funroll-loops:
| 操作 | So vs Go |
| bytes Buffer 写入 | 快 2-4x |
| 整数 Map Get(内置) | 快 3.5x |
| 整数解析 | 快 2x |
| 整数格式化 | 快 2-3x |
| String Builder | 快 2-4x,内存少 10-20% |
| 时间格式化(预定义布局) | 快 8.8x |
| 时间解析(预定义布局) | 快 4.9x |
| 字符串操作 | 快约 1.3x |
| time.Now() | 略慢(0.9x) |
时间格式化快 8.8x 是最亮眼的数据——Go 的 time.Format 因为要处理各种布局格式而相当复杂,而 Solod 直接用 C 的 strftime,对预定义布局有天然的优化优势。
竞品对比
| 维度 | Solod | TinyGo | Zig | Odin |
| 语法 | Go 子集 | Go 完整 | 自有 | 自有 |
| 运行时 | 零 | 轻量 | 无 | 最小 |
| GC | 无 | 有 | 无 | 无 |
| 并发 | 不支持 | goroutines | 手动 | 手动 |
| C 互操作 | 源码级零开销 | CGO | 源码级 | 源码级 |
| 学习成本 | 极低(会 Go 即会) | 低 | 中 | 中 |
| 输出 | 可读 C11 | 机器码 | 机器码 | 机器码 |
Solod 的独特定位:给 Go 开发者用的 C 语言。不是"C 替代品"(那是 Zig 的定位),而是"用 Go 语法写 C 级别的代码"。
适用场景:
1.嵌入式开发(零运行时、可控内存)
2.操作系统内核/驱动(无 GC 暂停)
3.C 库包装器(Go 语法 + 源码级互操作)
4.性能关键路径(多数场景优于 Go)
5.简单 CLI 工具和批处理(50% 的微服务没有并发需求)
工程总结
从 Go 工程的角度看,Solod 是"工具链复用"的优秀案例:
1.复用 Go 编译器前端:不做自己的词法分析器和语法解析器,直接用 go/packages 加载 AST 和类型信息。编译器只需实现 AST → C 的代码生成器,~20 个文件搞定。这个决策让 Solod 自动获得了 go vet、gopls、go fmt 的支持。
2.接口的本质实现:Go 接口 = {void* 数据 + 函数指针表}。Solod 用 C 把这个本质清晰地展示出来——没有 itab、没有 type assertion、没有 RTTI。如果你一直想理解 Go 接口的底层机制,读 Solod 生成的 C 代码是最好的方式。
3.手动内存管理的 Go 表达:Allocator 接口受 Zig 启发,但用 Go 语法表达。栈优先、显式堆分配的模式让内存布局完全可控。Go 的 defer 编译时内联展开也是精妙的设计——没有运行时的 defer 栈,只有编译器在正确的位置插入清理代码。
4.标准库的跨语言移植:把 Go 标准库移植为纯 C,不仅是为 Solod 服务——这些 C 实现可以独立使用。这是一个独特的价值主张:用 Go 的 API 设计哲学来改善 C 的标准库生态。
5.局限与风险:项目距 v0.1.0 约 50%,明确标注"Not for production"。一人项目(381 commits 全部来自 Anton)。语义差异(defer 块作用域、map 固定容量)意味着 Solod 代码的行为与 Go 不完全一致——不能用 go test 测试 Solod 的真实行为。依赖 GCC/Clang 扩展(alloca、__auto_type、statement expressions),不支持 MSVC。
最新版本:
项目主页:
https://antonz.org/solod/
https://github.com/solod-dev/solod