CGO Basics
10 examples to get you started with CGO & Interop - 7 basic and 3 intermediate.
Search across all documentation pages
10 examples to get you started with CGO & Interop - 7 basic and 3 intermediate.
gcc or clang; Xcode CLI tools on macOS).go env CGO_ENABLED should print 1 when a compiler is present.mkdir cgodemo && cd cgodemo && go mod init example.com/cgodemo.CGO_ENABLED=0 go build when testing fallbacks.import "C" with a C comment block enables cgo for this file.
package main
/*
#include <stdio.h>
*/
import "C"
func main() {
C.puts(C.CString("hello from cgo"))
}/* ... */ block immediately above import "C" is passed to the C compiler.C.puts maps to the C standard library function.C.CString allocates C memory; production code should defer C.free (covered in example 5).Related: CGO: Crossing the Go-C Boundary - What cgo costs at runtime
Linking uses libc symbols included via headers.
package main
/*
#include <math.h>
*/
import "C"
import "fmt"
func main() {
x := C.sqrt(2)
fmt.Println(float64(x))
}C.sqrt.float64(x) for Go APIs.LDFLAGS needed for libm on most platforms when using standard headers.Related: Calling C from Go & Exporting Go to C - Type conversions and directives
#cgo CFLAGS and #cgo LDFLAGSDirectives in the comment block configure compile and link flags.
package main
/*
#cgo CFLAGS: -Wall
#cgo LDFLAGS: -lm
#include <math.h>
*/
import "C"
func main() {
_ = C.cos(1)
}#cgo CFLAGS adds C compiler flags for this package only.#cgo LDFLAGS passes flags to the linker (-lm pulls libm where required).#cgo linux LDFLAGS: or #cgo darwin CFLAGS:.Related: Calling C from Go & Exporting Go to C - Full directive reference
${SRCDIR} expands to the directory containing the Go source file.
package main
/*
#cgo CFLAGS: -I${SRCDIR}/include
#include "greet.h"
*/
import "C"
func main() {
C.greet_from_c()
}include/greet.h and greet.c (or a prebuilt .a) beside the Go file.#cgo LDFLAGS: ${SRCDIR}/greet.o when compiling a local .c file into the package.Related: CGO & Interoperability Best Practices - Isolate cgo behind small packages
C.CString and C.freeC allocations are not garbage-collected by Go.
package main
/*
#include <stdlib.h>
#include <stdio.h>
*/
import "C"
import "unsafe"
func printC(s string) {
cs := C.CString(s)
defer C.free(unsafe.Pointer(cs))
C.puts(cs)
}
func main() {
printC("freed after use")
}C.CString copies bytes and adds a NUL terminator in the C heap.C.free unless C takes ownership per API contract.unsafe only for the unsafe.Pointer cast required by C.free.Related: FFI Safety: Pointers, C.CString & Free - Ownership rules in depth
[]byte to CUse C.CBytes for binary buffers and free when done.
package main
/*
#include <stdlib.h>
#include <string.h>
*/
import "C"
import "unsafe"
func main() {
data := []byte{0x01, 0x02, 0x03}
ptr := C.CBytes(data)
defer C.free(ptr)
_ = C.memcpy(ptr, ptr, C.size_t(len(data)))
}C.CBytes allocates len(data) bytes; contents are copied.C.GoBytes copies C memory back into a Go slice.Related: FFI Safety: Pointers, C.CString & Free - Slice and pointer passing
Ship a stub when CGO_ENABLED=0.
//go:build cgo
package demo
/*
#include <stdint.h>
*/
import "C"
func AddOne(n int) int {
return int(C.int(n) + 1)
}//go:build !cgo
package demo
func AddOne(n int) int {
return n + 1
}//go:build cgo files compile only when cgo is enabled.!cgo files let CI and cross-compiles succeed without a C compiler.Related: syscall & Pure-Go Alternatives to CGO - Fallback strategies
CGO_CFLAGS and CGO_LDFLAGSOverride flags globally for a single build without editing source.
CGO_CFLAGS="-O3 -I/opt/vendor/include" \
CGO_LDFLAGS="-L/opt/vendor/lib -lvendor" \
go build -o app .CGO_CFLAGS and CGO_LDFLAGS append to per-package #cgo lines.Related: CGO & Interoperability Best Practices - Reproducible build habits
Use build constraints in tests and small probe packages.
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Println("cgo enabled:", cgoEnabled())
fmt.Println("GOOS/GOARCH:", runtime.GOOS, runtime.GOARCH)
}//go:build cgo
package main
func cgoEnabled() bool { return true }//go:build !cgo
package main
func cgoEnabled() bool { return false }cgoEnabled across tagged files instead of parsing go env at runtime.Related: Interop Decision Guide: CGO vs RPC vs Rewrite - When cgo is worth the build cost
Hide import "C" behind an internal package.
// internal/native/add.go
package native
/*
#include "add.h"
*/
import "C"
func Sum(a, b int) int {
return int(C.add(C.int(a), C.int(b)))
}// main.go
package main
import (
"fmt"
"example.com/cgodemo/internal/native"
)
func main() {
fmt.Println(native.Sum(40, 2))
}internal/native imports C; main stays pure Go.native with build tags while fuzzing logic in pure Go wrappers.error values at this boundary.Related: Calling C from Go & Exporting Go to C - Exporting Go back to C
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).
Reviewed by Chris St. John·Last updated Jul 18, 2026