Low-level libraries, zero-copy conversions and C interop need these tools — and their rules are strict.
The unsafe package bypasses Go's type safety. unsafe.Sizeof, Alignof and Offsetof describe memory layout (useful for ordering struct fields to reduce padding). unsafe.Pointer can be converted to and from any pointer type and to uintptr, but only in the patterns listed in its documentation — for example converting *T1 to *T2 with compatible layout, or pointer arithmetic in one expression: unsafe.Pointer(uintptr(p) + off). A uintptr is just a number; the GC doesn't treat it as a reference, so storing one in a variable and converting it back later is a bug.
Since Go 1.20, unsafe.String, unsafe.StringData, unsafe.Slice and unsafe.SliceData give a sanctioned way to do zero-copy conversions between []byte and string. The contract: the bytes must never be modified afterwards, since strings are assumed immutable. Code using unsafe may break across Go versions and isn't covered by the compatibility promise; go vet catches some misuse.
cgo lets Go call C. A comment directly above import "C" holds C code or #includes, and you call C.func(...):
// #include <stdlib.h>
import "C"
cs := C.CString("hi") // malloc'd copy
defer C.free(unsafe.Pointer(cs))Costs and rules:
- Each call crosses from Go to C, costing far more than a Go call, and the thread is pinned for its duration — batch work per call.
- C may not keep Go pointers after the call returns, and memory passed to C must not contain Go pointers;
runtime.Pinner(Go 1.21) pins objects when needed. - cgo needs a C toolchain, complicates cross-compilation and static binaries.
CGO_ENABLED=0gives pure-Go, fully static builds — prefer pure-Go libraries when they exist.
Example
package main
import (
"fmt"
"unsafe"
)
type padded struct {
a bool // 1 byte + 7 padding
b int64 // 8
c bool // 1 byte + 7 padding
}
type packed struct {
b int64
a, c bool // 2 bytes + 6 padding
}
func bytesToString(b []byte) string {
return unsafe.String(unsafe.SliceData(b), len(b)) // zero-copy: b must not change
}
func main() {
fmt.Println(unsafe.Sizeof(padded{}), unsafe.Sizeof(packed{})) // 24 16
fmt.Println(unsafe.Offsetof(packed{}.c)) // 9
fmt.Println(bytesToString([]byte("no copy"))) // no copy
}Remember
- unsafe.Pointer conversions are valid only in the documented patterns
- Never store a uintptr and convert it back later — the GC can't see it
- cgo calls are costly and have pointer-passing rules; CGO_ENABLED=0 for pure-Go builds
Watch out for
- Mutating a []byte after converting it to a string with unsafe.String — breaks string immutability and can corrupt map keys.
- Forgetting to C.free a C.CString — C memory is not garbage collected.