
Go 调用 C 时 errno 怎么转成 Go error
Go 调用 C 函数(比如 open、connect)失败时,C 层只设 errno,而 Go 的 error 类型无法自动感知它——你得手动把 errno 值映射成有意义的 Go 错误。
最直接的方式是调用 syscall.Errno(errno).Error(),它会返回类似 "connection refused" 的字符串。但注意:这个映射依赖当前系统的 C 库(glibc/musl),在 Alpine(musl)和 Ubuntu(glibc)上返回的错误消息可能不同,不能用于日志结构化解析或客户端判断。
- 真正稳定的方案是用
syscall.Errno值本身做 switch 判断,比如if errno == syscall.ECONNREFUSED - 跨平台时优先比对
errno数值而非字符串,因为syscall.ECONNREFUSED在 Linux/macOS 上值相同,但 Windows 的syscall包不导出这些常量,需用x/sys/windows替代 - 别直接 return
fmt.Errorf("C call failed: %v", errno)—— 这样丢失了原始语义,下游无法用errors.Is(err, syscall.ECONNREFUSED)判断
Go 调用 Python(cgo + CPython API)怎么传错误码回 Python
用 cgo 调用 Python C API(如 PyEval_EvalCode)时,Python 内部异常不会自动变成 Go error;反过来,Go 函数被 Python 调用(通过 PyCapsule 或 ctypes),你也得把 Go 的 error 显式转成 Python 的 Exception。
关键点在于:Python C API 的错误传播靠全局状态(PyErr_SetString / PyErr_SetObject),不是返回值。Go 函数里不能直接调这些 C 函数(会破坏 goroutine 安全),必须确保调用发生在持有 GIL 的线程中。
立即学习“Python免费学习笔记(深入)”;
- 如果 Go 函数是被 Python 主线程调用(比如通过
ctypes.CDLL),可在入口加runtime.LockOSThread(),再调PyGILState_Ensure() - 把 Go error 转成 Python 异常:用
PyErr_SetString(PyExc_RuntimeError, err.Error());若要自定义类型,得先用PyErr_NewException创建类,再PyErr_SetObject - 切记调完立刻
PyGILState_Release,否则 Python 线程卡死;且 Go 的error字符串不能含 \0,否则PyErr_SetString截断
为什么不要用字符串匹配做跨语言错误码统一
有人试图在 Go 层把 syscall.EINVAL、Python 的 ValueError、C 的 EINVAL 全映射到一个字符串如 "invalid_argument",再让上下游解析这个字符串——这在实际协作中很快崩坏。
问题不在实现难度,而在演化成本:Python 升级后 ValueError 可能带新字段,Go 的 HTTP handler 返回的 400 Bad Request 对应多个底层错误,C 库换版本后 errno 含义微调。字符串成了隐式协议,没人维护文档,也没法做类型安全校验。
- 接口层错误码建议用整数 ID(如 Protobuf enum),由 ID 查表生成各语言本地 error 实例
- Go 侧可用
var ErrInvalidArg = errors.New("rpc: invalid argument"),同时暴露ErrInvalidArg.Code() uint32方法返回固定 ID - Python 侧用
raise RpcError(code=1001),构造时查表转成对应内置异常,而不是拼字符串
CGO_ENABLED=0 时怎么处理 C 层错误映射
禁用 cgo 后,syscall 包仍可用(它是纯 Go 实现),但像 unix.Open 这类函数返回的是封装后的 error,内部已做了 errno → error 映射;你没法再拿到原始 errno 值。
这意味着:如果你依赖某个 C 库的特定 errno(比如某个硬件驱动返回的私有错误码 ENOTSUPP),禁用 cgo 后这部分信息就丢了——unix.Syscall 不暴露 errno,unix.Errno 也不包含非标准值。
- 检查目标 C 库是否提供纯 Go 封装(如
golang.org/x/sys/unix已覆盖大部分标准 errno,但不包括厂商扩展) - 若必须用私有 errno,只能保留 cgo,且在构建脚本里强制
CGO_ENABLED=1,并加// #cgo LDFLAGS: -lfoo声明依赖 - 交叉编译时尤其注意:Android NDK 或 iOS toolchain 下,
errno值可能和 Linux 不同,别硬编码数值比较
跨语言错误映射最麻烦的从来不是转换逻辑本身,而是错误语义的边界模糊——同一个 errno 在不同上下文代表不同含义,而 Python 的 Exception 层级又比 Go 的 error 更重。留出一层可插拔的错误翻译器,比写死映射更可持续。










