cgo
Use cgo only when a real interoperability need exists and contain the boundary carefully.
Canonical guidance
- use cgo only for real interoperability or platform needs
- isolate cgo behind a narrow package boundary
- make ownership, allocation, and lifetime rules explicit
- remember cgo affects portability, build complexity, and performance characteristics
Use when
- calling native libraries
- binding to platform APIs
- bridging to existing C code that cannot be replaced reasonably
Avoid
- casual cgo adoption in otherwise pure-Go projects
- leaking C types through wide parts of the codebase
- unclear cross-language ownership
Preferred pattern
package sqliteffi
/*
#cgo pkg-config: sqlite3
#include <sqlite3.h>
*/
import "C"
func Version() string {
return C.GoString(C.sqlite3_libversion())
}
Anti-pattern
- letting cgo concerns infect unrelated packages and business logic
Explanation: This anti-pattern is tempting during fast integration work, but once C-facing details spread across packages the portability and maintenance costs multiply quickly.
Why
- cgo is a sharp but valid tool
- the boundary cost is architectural, not just syntactic