Go

What are the cgo pointer-passing rules, and how does runtime.Pinner help?

Question 427HardGo 1.22 to 1.25

The GC may move goroutine stacks and must be able to see every Go pointer. The rules, enforced at runtime when GODEBUG=cgocheck=1 (the default):

  1. Go may pass a Go pointer to C only if the memory it points to contains no Go pointers. Passing a []byte buffer is fine. Passing a struct with a *T field or a string field is not.
  2. C must not keep a Go pointer after the call returns (no storing it in a global, no using it from another thread later).
  3. Go code may not store a Go pointer in C memory, and a Go function called from C (via //export) may not return a Go pointer (so no strings, slices, maps or channels). Pinned pointers are the exception.
// OK: pointer to Go memory that has no Go pointers inside, used only during call
buf := make([]byte, 4096)
n := C.fill((*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf)))

// Need C to hold onto Go objects? Use runtime/cgo.Handle (an opaque integer):
h := cgo.NewHandle(myGoValue)   // import "runtime/cgo"
C.register_callback(C.uintptr_t(h))
// later in the exported callback: v := cgo.Handle(id).Value(); h.Delete()

runtime.Pinner (Go 1.21) pins a Go object so that C may keep the pointer, and lets Go memory that is passed to C contain pointers to pinned objects. You must call Unpin() afterwards.

Setting GOEXPERIMENT=cgocheck2 enables expensive write-barrier checks for debugging. When C needs its own long-lived data, allocate it with C.malloc and free it yourself.

More on Modules, Packages & Tooling

All 36 Modules, Packages & Tooling questions