Solod:用 Go 语法写 C,零运行时的跨平台开发利器

2 阅读7分钟

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.hgenerated/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_intint64_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 的设计原则非常明确,值得单独拎出来看:

  1. 极简优先 — 新功能默认被拒绝,除非有极强的真实用例
  2. 内置不分配堆 — 语言层面的 map、slice、make 都在栈上,堆分配必须显式
  3. Go 兼容性 — So 代码在语法上 100% 是合法的 Go 代码
  4. C 优先 — So 不是 C 的替代品,是"写 C 的更好方式"
  5. 性能驱动 — 有 benchmark,目标是对标或超越原生 Go

这种"克制"在当前语言工具百花齐放的环境里反而稀缺。作者明确拒绝了 generics、iterators、closures,这种决策需要很大的定力。

与类似方案对比

SolodTinyGoGoC
运行时有(轻量 GC)有(完整 runtime)
GC
产物大小极小(KB级)小(MB级)中(MB级)看实现
C 互操作零开销CGOCGO原生
并发goroutinegoroutine需自己实现
开发体验Go 级别Go 级别Go 级别C 级别
跨平台看编译器支持支持看编译器

总结

Solod 是一个非常特立独行的项目。它不试图成为"更好的 Go",也不想取代 C。它精确地瞄准了一个缝隙:

当你需要 Go 的开发体验,但又需要 C 的运行时控制力时。

这个缝隙确实存在——嵌入式开发者、游戏引擎开发者、追求极致性能的库作者,他们懂 C 但不想手写 C。So 给了他们第三个选择。

当然,项目还很年轻,标准库不够完善,不适合生产环境。但方向是对的,设计哲学是成熟的,值得持续关注。

如果你对 So 感兴趣,可以试试 Playground,在线体验 Go → C 的翻译效果。


本文基于 Solod 项目实测,测试环境:macOS ARM64,So 版本 latest (2026-04-21)。