go-cgo/docs/RFC.md
2026-04-03 20:09:13 +01:00

3.7 KiB

module repo lang tier depends tags
dappco.re/go/cgo core/go-cgo go lib
code/core/go
cgo
c-interop
bindings
ffi

go-cgo — Standard CGo Harness

Shared CGo boilerplate for all Core packages that cross the Go/C boundary. One harness, zero use-after-free bugs, every CGo package uses it.

Module: dappco.re/go/cgo Repository: core/go-cgo


1. Overview

Multiple Core packages use CGo: go-blockchain/crypto, go-mlx, go-rocm. Each rolls its own buffer allocation, string conversion, and error mapping. This package centralises that into tested, safe primitives.


2. Buffer Management

// NewBuffer allocates a C buffer with automatic cleanup tracking.
//
//   buf := cgo.NewBuffer(32)
//   defer buf.Free()
//   buf.CopyFrom(goSlice)
//   result := buf.Bytes()
type Buffer struct { /* ... */ }

func NewBuffer(size int) *Buffer
func (b *Buffer) Free()
func (b *Buffer) CopyFrom(src []byte) int
func (b *Buffer) Bytes() []byte
func (b *Buffer) Ptr() unsafe.Pointer
func (b *Buffer) Len() int
func (b *Buffer) IsFreed() bool

2.1 Safety

  • Double-free panics with descriptive message
  • Use-after-free panics with descriptive message
  • IsFreed() allows conditional cleanup in complex flows

3. C Function Calls

// Call wraps a C function call with Go error mapping.
//
//   err := cgo.Call(C.my_function, buf.Ptr(), cgo.SizeT(len))
//   // Returns Go error if C returns non-zero
func Call(fn unsafe.Pointer, args ...interface{}) error

// SizeT converts Go int to C size_t safely.
//
//   n := cgo.SizeT(len(data))
func SizeT(n int) C.size_t

// Int converts Go int to C int safely.
//
//   rc := cgo.Int(returnCode)
func Int(n int) C.int

4. String Conversion

// GoString converts a C string to Go string safely.
//
//   goStr := cgo.GoString(cStr)
func GoString(cs *C.char) string

// CString converts a Go string to C string. Caller must free.
//
//   cStr := cgo.CString(goStr)
//   defer cgo.Free(unsafe.Pointer(cStr))
func CString(s string) *C.char

// Free releases C-allocated memory.
//
//   cgo.Free(ptr)
func Free(ptr unsafe.Pointer)

5. Scoped Allocations

// Scope tracks multiple C allocations and frees them all at once.
//
//   scope := cgo.NewScope()
//   defer scope.FreeAll()
//   buf1 := scope.Buffer(32)
//   buf2 := scope.Buffer(64)
//   str := scope.CString("hello")
//   // All freed on scope.FreeAll()
type Scope struct { /* ... */ }

func NewScope() *Scope
func (s *Scope) Buffer(size int) *Buffer
func (s *Scope) CString(str string) *C.char
func (s *Scope) FreeAll()

6. Error Mapping

// Errno converts a C errno value to a Go error.
//
//   err := cgo.Errno(rc)
func Errno(rc C.int) error

// WithErrno calls a C function and returns the errno as a Go error.
//
//   result, err := cgo.WithErrno(func() C.int {
//       return C.my_function(args...)
//   })
func WithErrno(fn func() C.int) (int, error)

7. Implementation Priority

  1. Buffer (NewBuffer, Free, CopyFrom, Bytes, Ptr, Len, IsFreed)
  2. String conversion (GoString, CString, Free)
  3. Scope (NewScope, Buffer, CString, FreeAll)
  4. Call wrapper (Call, SizeT, Int)
  5. Error mapping (Errno, WithErrno)
  6. Safety panics (double-free, use-after-free)
  7. Tests for all of the above

8. Consumers

Package Current Boilerplate Saves
go-blockchain/crypto Manual buffer alloc + free ~50 lines
go-mlx Manual C array management ~40 lines
go-rocm Manual kernel buffer handling ~60 lines
CorePy (future) CPython embedding via cgo ~80 lines

Changelog

  • 2026-04-02: Initial RFC from Charon's request. Standard CGo harness for all Core packages.