syscall & Pure-Go Alternatives to CGO
Many tasks that once required cgo now have pure-Go paths: golang.org/x/sys for syscalls, community ports of crypto and compression libraries, and stdlib packages that wrap OS APIs directly.
Search across all documentation pages
Many tasks that once required cgo now have pure-Go paths: golang.org/x/sys for syscalls, community ports of crypto and compression libraries, and stdlib packages that wrap OS APIs directly.
Choosing pure Go first keeps cross-compilation, static binaries, and CI simpler.
Prefer maintained pure-Go libraries when they cover your platform matrix and performance needs.
Reach for cgo when a vendor SDK, hardware driver, or OS API exists only as C headers without a safe Go wrapper.
The syscall package is deprecated for new code; use x/sys/unix, x/sys/windows, or stdlib os/net instead.
Quick-reference recipe card - copy-paste ready.
import (
"golang.org/x/sys/unix"
)
func openReadOnly(path string) (int, error) {
fd, err := unix.Open(path, unix.O_RDONLY, 0)
if err != nil {
return -1, err
}
return fd, nil
}When to reach for this:
CGO_ENABLED=0.GOOS, GOARCH).x/sys or stdlib wrapper already exists for the API.List directory entries with x/sys on Unix instead of cgo-wrapped libc.
package main
import (
"fmt"
"os"
"golang.org/x/sys/unix"
)
func main() {
dir, err := os.Open(".")
if err != nil {
panic(err)
}
defer dir.Close()
// Prefer os.ReadDir in application code; unix.ReadDirent shows x/sys shape
buf := make([]byte, 4096)
for {
n, err := unix.ReadDirent(int(dir.Fd()), buf)
if err != nil || n == 0 {
break
}
// Parse buf per unix.ParseDirent in production helpers
fmt.Printf("read %d bytes of dirent data\n", n)
}
}What this demonstrates:
os.File feed unix syscalls without cgo.golang.org/x/sys tracks platform differences per GOOS.os APIs; x/sys is for gaps.go build with CGO_ENABLED=0 still succeeds.golang.org/x/sys is the maintained home for low-level constants and functions; the stdlib syscall package is frozen for compatibility.//go:embed for data tables instead of linking C.| Signal | Lean pure Go | Lean cgo |
|---|---|---|
Platform APIs in x/sys | Yes | No |
Vendor .h + .so SDK | No | Yes |
CGO_ENABLED=0 required | Yes | No |
| Decades-old C library with tests | Maybe port | Often wrap |
| Hardware DMA / GPU driver | Rare in pure Go | Usually cgo |
| Need | First look |
|---|---|
| Files, env, processes | os, os/exec |
| Sockets | net, syscall constants via x/sys |
| Terminal / ioctl | golang.org/x/term, x/sys/unix |
| SQLite | modernc.org/sqlite (pure) vs mattn/go-sqlite3 (cgo) |
| Compression | compress/*, klauspost/compress |
| Crypto | crypto/* stdlib |
//go:build !cgo
package db
import _ "modernc.org/sqlite" // database/sql driver, no cgoMirror with //go:build cgo files when you must support both driver backends behind one sql.Open DSN scheme.
syscall package for new code - Misses fixes in x/sys. Fix: Import golang.org/x/sys/unix or windows and migrate call sites.mattn/go-sqlite3 in static CI - Requires cgo and musl/glibc care. Fix: Use modernc.org/sqlite when CGO_ENABLED=0 is non-negotiable.x/sys definitions or official OS docs; add build tags per arch.!cgo implementations or clear documentation of hard cgo requirement.| Alternative | Use When | Don't Use When |
|---|---|---|
golang.org/x/sys | Documented syscalls per GOOS | API exists only in vendor C headers |
Stdlib os/net | Everyday file and network I/O | You need raw epoll/kqueue control |
| Pure-Go third-party port | Feature parity and license OK | Port is unmaintained or incomplete |
| cgo wrapper | Official SDK is C-only | A pure port is production-proven for you |
| External C service | Isolation beats binary simplicity | Ultra-low latency in-process |
No - it remains for stdlib compatibility but is deprecated for new application code.
Use x/sys for new low-level calls.
No - it is pure Go with platform-specific files and assembly where needed.
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build works for packages without import "C".
No C cross-compiler required.
When you need specific SQLite compile-time options or extensions only exposed via the C API.
Otherwise evaluate pure drivers first.
Yes - keep syscalls in pure Go packages and isolate cgo to wrappers.
Avoid importing C from hot HTTP handlers.
Syscalls differ entirely on js/wasm and wasip1.
See the WebAssembly overview in this section; cgo is not available there.
golang.org/x/sys/unix exposes many of them on Linux and BSDs.
Verify support for your GOOS/GOARCH combination.
Search pkg.go.dev with CGO_ENABLED=0 build tags, check Awesome Go lists, and read driver READMEs for cgo requirements.
Yes - memory safety of Go applies; fewer native blobs to audit.
C libraries still need review when wrapped.
Contribute to golang.org/x/sys with generated constants from official headers.
Last resort: small cgo package behind internal API.
Often yes - shelling out to openssl or ffmpeg avoids linking them.
Trade process overhead for build simplicity.
Idiomatic Go modules ship !cgo builds when feasible and document cgo-only accelerators.
Consumers thank you in CI and cross-compile pipelines.
CGO_ENABLEDStack 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