Search docsSearch this language
Getting started
Release candidatev0.20260809.0-rc.5

These docs describe the current coordinated release candidate.

Getting started

Hello World

Compile one C function into a Go package and call it from a Go test.

This 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.