メインコンテンツへスキップ
#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段階に分けます。

  1. 開発時: パッケージ作者が go tool cgo -gen-binding でバインディングファイルを生成します。この段階だけは C コンパイラを使い、DWARF 情報(デバッグ情報)から型のレイアウトなどを取り出します。手書きでも構いません。
  2. ビルド時: 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 も対象にするかが質問として挙がっています。

関連リンク