Solod:用 Go 语法写 C,零运行时的跨平台开发利器
源码:github.com/solod-dev/s…
许可证:MIT
状态:早期阶段(Not for production)
引言
作为 Go 开发者,你有没有遇到过这些场景:
- 想写一个 CLI 工具,但 Go 编译出来的二进制还是太大,运行时依赖太多
- 想写嵌入式代码,但 C 的开发体验实在折磨人
- 想给 C 项目加一层逻辑,但 CGO 的开销让你望而却步
- 想热更新游戏逻辑,但脚本语言的性能不够
如果你对其中任何一个点头了,那你应该了解一下 Solod(So)。
Solod 是什么?
Solod 是一个 Go 的严格子集,它能把 Go 代码翻译成 可读的 C11 代码。用作者的话说:
Go in, C out. You write regular Go code and get readable C11 as output.
不是解释执行,不是字节码虚拟机,是真正的 源码级转译——你写 Go,它吐 C,然后你想怎么编译就怎么编译。
核心特性
| 特性 | 说明 |
|---|---|
| 零运行时 | 无 GC、无引用计数、无隐式内存分配 |
| 默认栈分配 | 所有变量默认在栈上,堆分配需显式申请 |
| Go 工具链兼容 | 语法高亮、LSP、linting、go test 开箱即用 |
| 原生 C 互操作 | So 调用 C、C 调用 So,无 CGO、零开销 |
| 可读的 C 输出 | 生成的 .h/.c 文件结构清晰,人工可读 |
| 跨平台 | 产物是标准 C,任何有 C 编译器的平台都能编译 |
支持与不支持
✅ 支持:
- struct、method、interface、slice、map、多返回值、defer
- if/else、switch、for、goto
- 字符串(UTF-8)、数组、枚举、错误处理、panic
- any(翻译为
void*)、指针、nil - 标准库:time、math、fmt、os、slices、strings、io 等
❌ 不支持(且短期内不会支持):
- channels、goroutines、closures
- generics、iterators、complex numbers
这是有意为之——Solod 的定位不是"Go 的另一个实现",而是"写 C 的更好方式"。
动手试试
安装
go install solod.dev/cmd/so@latest
Hello World
创建项目:
mkdir hello-so && cd hello-so
go mod init hello-so
go get solod.dev@latest
写代码(main.go):
package main
import "solod.dev/so/time"
type Person struct {
Name string
Age int
}
func (p *Person) Sleep() int {
p.Age += 1
return p.Age
}
func main() {
p := Person{Name: "Alice", Age: 30}
p.Sleep()
println(p.Name, "is now", p.Age, "years old.")
year := time.Now().Year()
println("The year is", year)
}
运行:
so run .
输出:
Alice is now 31 years old.
The year is 2026
看起来和普通 Go 没区别对吧?这就是 So 的设计目标——语法不变,产物变了。
看看生成的 C 代码
so translate -o generated .
会生成 generated/main.h 和 generated/main.c,内容非常清晰:
// main.h
#pragma once
#include "so/builtin/builtin.h"
#include "so/time/time.h"
typedef struct main_Person {
so_String Name;
so_int Age;
} main_Person;
so_int main_Person_Sleep(void* self);
// main.c
#include "main.h"
so_int main_Person_Sleep(void* self) {
main_Person* p = (main_Person*)self;
p->Age += 1;
return p->Age;
}
int main(void) {
main_Person p = (main_Person){.Name = so_str("Alice"), .Age = 30};
main_Person_Sleep(&p);
so_println("%.*s %s %" PRId64 " %s", p.Name.len, p.Name.ptr, "is now", p.Age, "years old.");
so_int year = time_Time_Year(time_Now());
so_println("%s %" PRId64, "The year is", year);
}
注意几个关键点:
string变成了so_String(一个{ptr, len}结构体),零拷贝int变成了so_int(int64_t)- 方法接收者变成了
void* self+ 类型转换,典型的 C 风格 - 没有任何运行时初始化代码,也没有隐藏的内存分配
这就是 So 的哲学:你写 Go,但你知道自己在写什么。
跨平台编译实战
So 的跨平台能力完全取决于你机器上的 C 编译器,这是它的优势。
macOS 编译 macOS
so build -o hello-so .
./hello-so
# Hello, So!
macOS 交叉编译 Windows exe
安装 MinGW 交叉编译器:
brew install mingw-w64
一行搞定:
CC=x86_64-w64-mingw32-gcc so build -o hello-so.exe .
验证产物:
file hello-so.exe
# hello-so.exe: PE32+ executable (console) x86-64, for MS Windows
编译产物只有 143KB,没有任何运行时依赖。
编译到其他平台
同理,只要装好对应的交叉编译工具链:
# ARM Linux(树莓派等)
CC=aarch64-linux-gnu-gcc so build -o hello-so-arm64 .
# 嵌入式(无 OS)
CC=arm-none-eabi-gcc so build -o firmware.elf .
So 本身不做任何平台假设,跨平台这件事完全交给 C 编译器处理。
适用场景分析
基于实际测试和项目特性,以下是我认为 So 最有价值的应用场景:
1. 嵌入式 / 实时系统 ⭐⭐⭐⭐⭐
痛点: 嵌入式开发通常只能用 C,开发体验差,类型安全弱,容易出 bug。
So 的优势: Go 的类型系统 + C 的底层控制,没有 GC 中断,纯栈分配,产物无运行时。
对比: 比 Rust 门槛低,比 TinyGo 更干净(零运行时),比 C 更安全。
2. 游戏逻辑热更新 ⭐⭐⭐⭐
痛点: 游戏引擎用 C++ 写,但业务逻辑频繁迭代,每次改动都要重新编译整个引擎。
So 的优势: 逻辑用 So 写 → 编译成动态库 → 引擎热加载。GC 暂停问题不存在。
限制: 需要自行管理内存,适合有经验的团队。
3. CLI 工具 / 最小化容器 ⭐⭐⭐⭐
痛点: Go CLI 在 Docker 最小镜像中还是偏大,或受限环境不允许 Go runtime。
So 的优势: 143KB 的 exe,零依赖,可以塞进任何环境。
对比: 开发体验接近 Go,产物接近 C。
4. 高性能库 / SDK 封装层 ⭐⭐⭐⭐
痛点: 你有一个 C 库,想提供安全的高层 API,但手写 C 太痛苦。
So 的优势: 双向互操作零开销,So 代码可以直接 link 进 C 项目,也可以暴露 API 给 C 调用。
场景: 数据库驱动、网络协议库、编解码库。
5. 数据库 / 存储引擎 ⭐⭐⭐
痛点: 存储引擎需要精确控制内存布局,GC 会干扰缓存行为。
So 的优势: 无 GC、内存布局可控、标准库提供显式堆分配。
限制: 复杂项目需要较强的 C 背景。
不适合的场景
- 高并发服务端:没有 goroutine/channel,需要自己管理并发
- 快速原型开发:需要手动管理内存,迭代速度不如原生 Go
- 需要丰富第三方库:无法直接使用 Go 的标准库和第三方包
- 团队 C 经验不足:生成的 C 代码最终还是要懂的
设计哲学
Solod 的设计原则非常明确,值得单独拎出来看:
- 极简优先 — 新功能默认被拒绝,除非有极强的真实用例
- 内置不分配堆 — 语言层面的 map、slice、make 都在栈上,堆分配必须显式
- Go 兼容性 — So 代码在语法上 100% 是合法的 Go 代码
- C 优先 — So 不是 C 的替代品,是"写 C 的更好方式"
- 性能驱动 — 有 benchmark,目标是对标或超越原生 Go
这种"克制"在当前语言工具百花齐放的环境里反而稀缺。作者明确拒绝了 generics、iterators、closures,这种决策需要很大的定力。
与类似方案对比
| Solod | TinyGo | Go | C | |
|---|---|---|---|---|
| 运行时 | 无 | 有(轻量 GC) | 有(完整 runtime) | 无 |
| GC | ❌ | ✅ | ✅ | ❌ |
| 产物大小 | 极小(KB级) | 小(MB级) | 中(MB级) | 看实现 |
| C 互操作 | 零开销 | CGO | CGO | 原生 |
| 并发 | ❌ | goroutine | goroutine | 需自己实现 |
| 开发体验 | Go 级别 | Go 级别 | Go 级别 | C 级别 |
| 跨平台 | 看编译器 | 支持 | 支持 | 看编译器 |
总结
Solod 是一个非常特立独行的项目。它不试图成为"更好的 Go",也不想取代 C。它精确地瞄准了一个缝隙:
当你需要 Go 的开发体验,但又需要 C 的运行时控制力时。
这个缝隙确实存在——嵌入式开发者、游戏引擎开发者、追求极致性能的库作者,他们懂 C 但不想手写 C。So 给了他们第三个选择。
当然,项目还很年轻,标准库不够完善,不适合生产环境。但方向是对的,设计哲学是成熟的,值得持续关注。
如果你对 So 感兴趣,可以试试 Playground,在线体验 Go → C 的翻译效果。
本文基于 Solod 项目实测,测试环境:macOS ARM64,So 版本 latest (2026-04-21)。