Getting started
Hello World
Compile one C function into a Go package and call it from a Go test.Maintained byC2Go contributorsThis walkthrough compiles one C addition function into a Go package and calls it from a Go test. It assumes the matching SDK is on PATH and C2GO_TARGET contains the target triple for your downloaded archive.
1. Create a Go module
mkdir hello-c2go
cd hello-c2go
mkdir .c2go
go mod init example.com/hello-c2go
go get github.com/c2gohq/c2go_libc@v0.20260809.0-rc.5
Set the triple that matches your SDK. For macOS arm64:
export C2GO_TARGET=aarch64-apple-darwin
2. Write your C function
Create .c2go/input.c:
#include <stdint.h>
#include <c2go.h>
c2go_extern int add(int a, int b) {
return a + b;
}
c2go_extern marks a defined C function for export. The default Go name is CamelCase by underscore-separated segment: add becomes Add, and sqlite3_open becomes Sqlite3Open.
C int remains 32 bits on these 64-bit targets, so the generated signature is func Add(a, b int32) int32, not machine-word-sized Go int. The test’s 20 and 22 are untyped constants and are accepted directly.
Keep C source and intermediate compiler outputs in the hidden .c2go/ directory. The Go command rejects ordinary .c files in a package directory when cgo is not enabled; dot-prefixed directories are ignored by package discovery.
3. Compile with c2go-clang
c2go-clang --target="$C2GO_TARGET" \
-fc2go \
-fc2go-package=example.com/hello-c2go/translated \
-O2 \
-fc2go-emit-plan9-asm=.c2go/translated.s \
-fc2go-emit-manifest=.c2go/translated.json \
.c2go/input.c
The compiler writes .c2go/translated.s and .c2go/translated.json. This emit path does not produce a usable translated.o object file.
4. Generate the Go package
mkdir -p translated
c2go-bind \
--out=translated \
--sidecar=.c2go/translated.json \
.c2go/translated.s
The output directory now contains generated Go declarations, assembly, ABI guards, runtime glue, and generated-code licensing records. Keep those licensing files when redistributing the generated package.
5. Call it from Go
Create add_test.go in the module root:
package hello_c2go_test
import (
"testing"
"example.com/hello-c2go/translated"
)
func TestAdd(t *testing.T) {
got := translated.Add(20, 22)
if got != 42 {
t.Fatalf("translated.Add(20, 22) = %d, want 42", got)
}
}
Run the test:
go test ./...
Expected result:
ok example.com/hello-c2go
? example.com/hello-c2go/translated [no test files]
What just happened
The compiler lowered C into Plan 9 assembly for the selected Go OS and architecture. The manifest told c2go-bind that add is exported, which target and contract epochs the assembly uses, and which runtime support is required. c2go-bind generated the Go-visible Add declaration and copied the matching assembly into the package.
Next, inspect the build pipeline and learn why target, package path, assembly, and manifest must remain coordinated.