cmd/cgo: add cgo without a C toolchain as a GOEXPERIMENT
要約
概要
#81450「C ツールチェーンなしの cgo」は現在も議論中の proposal です。本 proposal は、その実装を本体リポジトリ(golang/go)で先行して進められるように、cgobindings という GOEXPERIMENT(実験的機能の有効/無効を切り替えるビルド時フラグ)を追加するものです。
ステータス変更
likely_accept → accepted
2026年9月24日に @aclements がレビューグループを代表して likely accept を宣言しました。その後1週間、反対意見は出ず、10月1日に「No change in consensus」として正式に承認されました。本 proposal は本体 #81450 の採否を決めるものではありません。実験用のフラグで機能を囲った状態でコードを追加し、試せるようにするための手続きです。範囲が限られており、失敗しても元に戻しやすいため、議論はほとんど起きませんでした。
技術的背景
現状の問題点
import "C" を含むパッケージをビルドするには、C コンパイラが必須です。理由は2つあります。cgo コマンドが C プリアンブル(import "C" 直前のコメントに書く C コード)を C コンパイラで解析していること、そして生成されるグルーコード(Go と C の呼び出しをつなぐ中継コード)や runtime/cgo 自体に C コードが含まれていることです。このため、ビルド済みの .so/.a を呼び出すだけのパッケージでも、利用者側に C ツールチェーンが要ります。クロスコンパイルではさらに、C のクロスコンパイラや sysroot の準備も必要になります。
親 proposal #81450 はこの問題を次の方法で解決しようとしています。
- C のインターフェースを Go の構文で書いた「バインディングファイル」で表す(例:
//cgo:binding C.computeという注釈を付けた関数宣言) - C の呼び出し規約(ABI)への変換は cgo コマンドが Go とアセンブリで生成する
runtime/cgoを Go とアセンブリで書き直す
ただし、バインディングの書式(注釈方式か、別ファイル方式か)など未確定の点が多く残っています。
提案された解決策
internal/goexperimentにcgobindingsフラグを追加する- デフォルトは無効。親 proposal の結論が出るまでは、関連する実装をこのフラグの内側に置く
- #81450 が承認された場合はフラグをデフォルトで有効にしてから削除し、却下された場合は実験とその内側のコードをすべて削除する
実装 CL 832864(internal/goexperiment: add cgobindings experiment)はすでにマージされています。この CL はflags.goとexp_cgobindings_on.go/exp_cgobindings_off.goを追加しています。
これによって何ができるようになるか
- Go チームが段階的に実装できる: バインディング生成(
go tool cgo -gen-binding)、cmd/go の対応、ABI トランポリン(呼び出し規約を変換する中継コード)の生成といった作業を、デフォルトの挙動を変えずに本体へ取り込めます。 - 利用者が試してフィードバックできる: purego や mattn/go-sqlite3 のようなライブラリの作者が、実際のコードで設計を検証できます。#81450 では、union 型の扱い、可変長引数、関数ポインタ、静的ライブラリへの対応などが論点に挙がっています。
- やり直しが簡単になる: 却下された場合や設計が大きく変わった場合でも、フラグの内側のコードを消すだけで済みます。
コード例
// Before: C ツールチェーン必須の従来の cgo
package mypkg
/*
struct point { float x; float y; };
float compute(struct point p0);
*/
import "C"
// After(実験中・書式は #81450 の現時点の案):
// GOEXPERIMENT=cgobindings で、C コンパイラなしでビルドすることを目指す
//go:build darwin
package mypkg
import "C"
//cgo:binding C.point
type Point struct {
x, y float32
}
//cgo:binding C.compute
func Compute(p0 Point) float32
GOEXPERIMENT=cgobindings go build ./...
なお、バインディングの書式は議論中のため、最終的な仕様は変わる可能性があります。
議論のハイライト
- 本 issue にはボットのコメントと承認手続き以外の議論がありません。新機能を GOEXPERIMENT で先行実装するのは Go で確立した進め方であり、異論は出ませんでした。
- 親 #81450 では、@cherrymui が「この方式でも cgo は有効なので、
CGO_ENABLED=0を切り替えのきっかけにするのは不適切」と述べています。C ツールチェーンなしのビルドは、cgo を無効にすることとは別物として扱われています。 - @ianlancetaylor は、C の呼び出し規約が複雑だと指摘しました(C で呼び出し規約を実装した libffi は約5万行あります)。@cherrymui は、だからこそ実行時ではなくビルド時に静的に処理する、と答えています。こうした設計上の論点は実装して確かめる必要があり、実験フラグを設ける理由の一つと考えられます。
- 親 proposal では、バインディングの書式(
//cgo:binding注釈方式か、別ファイル方式か)、可変長引数(//cgo:variadic案)、関数ポインタ(//cgo:funcptr案)などがまだ議論中です。 - gabyhelp のコメントからは、関連 CL の中に
cgo2、cgogoshapedbindingsといった別名の案があったことがうかがえます。最終的にcgobindingsという名前に決まりました。