#81450active
cmd/cgo: cgo without a C toolchain
新規提案
要約
AIによる要約であり、誤りを含む場合があります。
概要
import "C" を使うcgoパッケージを、C コンパイラなしでビルドできるようにする提案です。Google の Go チーム(matloob、cherrymui)が出しました。対象は、ビルド済みの C ライブラリ(.so/.a)を呼ぶだけで、C のソースコードを持たないパッケージです。C の型や関数の情報は「バインディングファイル」として Go の構文で書き、リポジトリに置いておきます。C ABI(C の呼び出し規約)との橋渡しをするグルーコードは、Go と Go アセンブリで生成します。
ステータス変更
(新規) → active
2026年9月に提案されてから、コミュニティで活発に議論されました。11月4日には、レビュー用に変更点を絞った cherrymui による「コンパクト版」仕様が出ています。プロトタイプ実装の CL もすでに数十本あり、GOEXPERIMENT=cgobindings の下で runtime/cgo を全 OS・アーキテクチャ向けに Go で書き直す作業まで進んでいます。設計が具体的になったため、11月12日に aclements が Proposal Review Meeting の active 列に追加しました。今後は毎週のレビュー会議で検討されます。
技術的背景
現状の問題点
- cgo パッケージをビルドするには、常に C ツールチェーンが必要です。ライブラリの作者だけでなく、
go getして使うエンドユーザーの環境にも必要になります。 - クロスコンパイルでは、C のクロスコンパイラ、sysroot、ヘッダーファイルを用意する必要があります。
- C ツールチェーンが必要になる箇所は2つあります。1つは C プリアンブル(
import "C"の直前のコメントに書く C コード)の解析、もう1つは cgo が生成する C のグルーコードのコンパイルです。さらにruntime/cgo自体にも C コードが含まれています。 - このため、
ebitengine/puregoのように、cgo を使わずに C を呼ぶ非公式な仕組みが広く使われています。purego はgo:linknameで runtime の内部に依存しており、壊れやすい状態です(#67401)。
提案された解決策
処理を2段階に分けます。
- 開発時: パッケージ作者が
go tool cgo -gen-bindingでバインディングファイルを生成します。この段階だけは C コンパイラを使い、DWARF 情報(デバッグ情報)から型のレイアウトなどを取り出します。手書きでも構いません。 - ビルド時:
cmd/cgoがバインディングファイルを読み、プラットフォームの C ABI に沿ったトランポリン(呼び出し規約を変換する中継コード)を Go とアセンブリで生成します。
主なディレクティブは次のとおりです。
//cgo:binding C.<name> ["<lib>"]: Go の type・const・var・本体のない func を C のシンボルに結び付けます。Go 側の名前とC.<name>のどちらでも参照できます。//cgo:variadic N: 可変長引数の C 関数を、固定引数の数を指定して非可変長の Go 関数として呼べるようにします。//cgo:funcptr: C の関数ポインタ型にCallメソッドを定義し、間接呼び出しを可能にします。//cgo:dynamic_library: 実行時にdlopenで読み込むライブラリを、型安全に扱えるようにします。新しい API としてruntime/cgo.Library(Open/Close/IsLoaded)が加わります。CGO_ENABLED=auto: 新しい値です。クロスコンパイル時のデフォルトになり、C ツールチェーンを使わないこのモードだけを有効にします。
このモードが自動で選ばれる条件は次の3つです。- パッケージに
.cなどの C ソースがない //cgo:binding(または//export)を含むCGO_CFLAGSが明示的に設定されていない
これによって何ができるようになるか
- 配布が楽になる: SQLite、librdkafka、tree-sitter などのバインディングを、エンドユーザーは C コンパイラなしで
go getしてビルドできます。 - クロスコンパイルが楽になる: macOS の CoreFoundation など OS の API を Linux からビルドできるようになり、Xcode が不要になります。GUI アプリや Android 向けの
c-sharedライブラリも C ツールチェーンなしで作れる見込みです。 - 動的ロードが型安全になる: Vulkan や OpenGL のように関数ポインタを実行時に取得する API も、purego のようなリフレクション的な呼び出しではなく、静的に型チェックされた形で呼べます。
コード例
// Before: 従来の cgo。ビルドするたびに C コンパイラが必要
package mypkg
/*
struct point { float x; float y; };
float compute(struct point p0);
*/
import "C"
func Use() float32 {
return float32(C.compute(C.struct_point{x: 1, y: 2}))
}
// After: バインディングファイル(生成または手書きし、リポジトリに置く)
//go:build darwin
package mypkg
import "C"
//cgo:binding C.point
type Point struct {
structs.HostLayout // C と同じメモリレイアウトにする
x, y float32
}
//cgo:binding C.compute "libgeom.dylib"
func Compute(p0 Point) float32 // C.compute としても呼べる
// 可変長引数の関数
//cgo:binding C.printf "_"
//cgo:variadic 1
func printfInt(*C.char, C.int)
// 実行時に読み込むライブラリ
//cgo:dynamic_library lib "libfoo.so"
var Lib FooLib // FooLib は runtime/cgo.Library を埋め込む
議論のハイライト
- バインディングの書式: 別案として、
C.名前空間だけで書く独立したファイル形式も検討されました。しかし議論の中で、//cgo:bindingの注釈方式のほうが有利な場面が見つかりました。たとえば Objective-C のobjc_msgSendには、1つのシンボルに複数のシグネチャを結び付ける必要があります。動的ロードで得た関数ポインタを Go 変数で参照する場合も、注釈方式のほうが自然に書けます(mikeschinkel、TotallyGamerJet)。 - C ABI をどこで扱うか: ianlancetaylor は、C の呼び出し規約を実装している libffi が約5万行あることを挙げ、その複雑さを指摘しました。cherrymui は、ABI の処理は Go ツールチェーン(
cmd/cgo)に集め、実行時ではなくビルド時に静的に解決するのが、ツールチェーン側の複雑さと利用者の負担のバランスとして妥当だと説明しています。 CGO_ENABLEDの意味: 「CGO_ENABLED=0は C ツールチェーンが不要という意味で使われている」という声が purego 利用者などから多く上がりました(qmuntal、hajimehoshi)。これを受けて、クロスコンパイル時のデフォルトを新しい値autoにする案が採られました。.aファイルの扱い: 静的ライブラリも対象範囲に含まれます。モジュールにバイナリを入れて配布することへのセキュリティ上の懸念も出ました。これについては、既存の.sysoファイルで同じことがすでにできること、この提案は「C ライブラリをどう配布するか」は変えないことが説明されています。- 未解決の論点: C の TSAN(スレッドサニタイザー)を使う場合の扱いは決まっていません。ライブラリ名の指定(
"<lib>")を必須にするかどうかも検討中です。指定を忘れると、ビルドは通っても実行時に必要なライブラリが読み込まれない問題が起こるためです。ほかにLibrary.Openに RTLD フラグを渡せるようにするか、c-archiveも対象にするかが質問として挙がっています。
関連リンク
- Proposal Issue github.com/golang/go
- Review Comment proposal review meeting
- Review Minutes
- レビュー用コンパクト版仕様
- #38917 proposal: cmd/cgo: add support for pre-compiled cgo packages
- #77386 proposal: x/tools/ffi: Native Go FFI Binding Generation
- #82111 android/arm64 で c-shared を内部リンクする件の追跡 issue
- #64663 cgo.Handle と CGO_ENABLED=0
- Proposal Issue #81450