unsafe Package — Specification¶
Focus: Precise reference for the
unsafepackage — what it provides, the rules that make its use sound, and what the compiler/runtime guarantees (or does not).Sources: -
unsafepackage: https://pkg.go.dev/unsafe - Go spec — Package unsafe: https://go.dev/ref/spec#Package_unsafe - Pointer rules: https://pkg.go.dev/unsafe#Pointer
1. What unsafe provides¶
| Type/Func | Purpose |
|---|---|
unsafe.Pointer | A pointer to arbitrary memory; no type, no GC scanning rules beyond "treat as pointer" |
unsafe.Sizeof(x) | Size in bytes of the type of x (compile-time constant) |
unsafe.Alignof(x) | Alignment in bytes (compile-time constant) |
unsafe.Offsetof(field) | Byte offset of field within its enclosing struct |
unsafe.Add(p, n) | (Go 1.17+) unsafe.Pointer(p + n) — typed pointer arithmetic |
unsafe.Slice(p, n) | (Go 1.17+) Construct []T from pointer + length |
unsafe.SliceData(s) | (Go 1.20+) *T to the underlying array of a slice |
unsafe.String(p, n) | (Go 1.20+) Construct a string from pointer + length |
unsafe.StringData(s) | (Go 1.20+) *byte to the underlying bytes of a string |
unsafe.Sizeof, Alignof, Offsetof are constant expressions; the others are runtime operations.
2. The unsafe.Pointer rules¶
The Go runtime treats unsafe.Pointer specially. The package doc lists six valid conversion patterns. Outside these, your code is undefined-behavior territory.
The six valid patterns:
- Conversion of
*T1tounsafe.Pointerto*T2— type-punning two pointer types. - Conversion of
unsafe.Pointertouintptr(and back) without intervening operations — for syscall/cgo arguments only. - Conversion of
unsafe.Pointertouintptrand back, with arithmetic, in one expression — for indexing into a known structure. - Conversion of an
unsafe.Pointerfromreflect.Value.Pointerorreflect.Value.UnsafeAddr— the result must be converted to a*Timmediately. - Conversion of an
unsafe.Pointerto/fromreflect.SliceHeader/StringHeader— deprecated; useunsafe.Slice/SliceData/String/StringData(Go 1.20+). - Conversion of
unsafe.Pointerreturned bysyscall.Syscalletc. — for low-level OS interop.
Pattern 3 is the most subtle: the entire arithmetic chain must be a single expression, because the GC may move the underlying object between statements (in practice it doesn't move objects, but the spec reserves the right).
3. The uintptr trap¶
uintptr is an integer, not a pointer. The GC does not consider it a reference; an object reachable only through a uintptr can be collected.
p := unsafe.Pointer(&x)
n := uintptr(p)
// x may be collected here if no other reference exists
back := (*T)(unsafe.Pointer(n)) // dereferencing may read freed memory
Use runtime.KeepAlive(x) to extend the original lifetime past the dereference, or never let the pointer become a uintptr outside the syscall/arithmetic patterns above.
4. Constant operations¶
var s struct {
a int32
b int64
}
const sz = unsafe.Sizeof(s) // 16 (with padding)
const off = unsafe.Offsetof(s.b) // 8 on 64-bit
const al = unsafe.Alignof(s) // 8 on 64-bit
These are compile-time constants and can appear in type-switch cases, array sizes, etc. Their values are platform-dependent.
5. unsafe.Add and unsafe.Slice (Go 1.17+)¶
unsafe.Add(p, len) — equivalent to unsafe.Pointer(uintptr(p) + len) but type-checked and easier to reason about.
unsafe.Slice(*T, len) — returns []T with the pointer as the backing array start, length and capacity equal to len. The underlying memory must be valid for len * sizeof(T) bytes.
These functions made the older reflect.SliceHeader approach largely obsolete.
6. unsafe.String, unsafe.StringData, unsafe.SliceData (Go 1.20+)¶
Provide a typed way to alias between []byte and string without the unsafe-pointer dance.
func b2s(b []byte) string {
return unsafe.String(unsafe.SliceData(b), len(b))
}
func s2b(s string) []byte {
return unsafe.Slice(unsafe.StringData(s), len(s))
}
Rules:
- The aliased memory must remain unchanged for the lifetime of the resulting view.
- Mutating a
[]bytewhose memory is aliased by a livestringviolates Go's immutable-string assumption — undefined behavior follows.
7. Alignment requirements¶
Some platforms (notably 32-bit ARM and some MIPS variants) require aligned access for 64-bit atomics. Misaligned unsafe.Pointer dereferences may fault.
type T struct { a int32; b int64 }
// On 64-bit: b has offset 8; on 32-bit, may be 4 — atomic ops on b panic
For atomics, atomic.Int64 (Go 1.19+) guarantees alignment via its struct layout, removing the historical 32-bit headache.
8. GC implications¶
The runtime tracks pointers to heap objects through typed references. unsafe.Pointer is treated as a pointer (the GC traces it). uintptr is not.
Consequences:
- Storing pointers as
unsafe.Pointerin heap objects works; the GC sees them. - Storing pointers as
uintptrin heap objects breaks the GC's view; the pointed-to object may be collected. - Cgo allocations (
C.malloc) live outside the Go heap; the GC never traces or frees them.
9. What unsafe cannot do¶
- Cannot create a
Pointerto a non-existent type at compile time. - Cannot bypass the language's type system at the call boundary (you still must cast through
unsafe.Pointer). - Cannot make atomically safe accesses to misaligned memory.
- Cannot enable behavior the runtime forbids (e.g., writing to a string's bytes).
unsafe is an escape hatch for the language's type system, not for the runtime's invariants.
10. Common idioms¶
| Pattern | Use case |
|---|---|
*(*T)(unsafe.Pointer(&otherT)) | Reinterpret a value as a different type (same size and layout) |
unsafe.Add(p, offset) | Indexing into a struct or array |
unsafe.Slice(*T, n) | Wrapping C-allocated memory as a Go slice |
unsafe.String(*byte, n) | Avoiding the copy from []byte to string (read-only) |
(*reflect.SliceHeader)(unsafe.Pointer(&s)) | Legacy slice-header access — replace with unsafe.SliceData |
11. Versioning notes¶
| Version | Addition |
|---|---|
| 1.17 | unsafe.Add, unsafe.Slice |
| 1.20 | unsafe.String, unsafe.StringData, unsafe.SliceData |
| 1.21+ | Minor clarifications in the pointer rules |
Code targeting older Go must use the legacy reflect.SliceHeader style.
12. Stability and the Go 1 compatibility promise¶
unsafe is excluded from the Go 1 compatibility promise. The exact rules can change between releases (rare but possible). New analyzers (vet checks) may flag patterns that previously compiled cleanly.
In practice, the unsafe.Pointer rules have been stable for years, but always re-read the package doc when upgrading.
13. Tooling¶
| Tool | Purpose |
|---|---|
go vet | unsafeptr analyzer warns on suspicious uintptr ↔ Pointer conversions |
go test -race | Detects data races on unsafe-accessed memory |
cgo | Provides C.malloc, C.free and the bridge for unsafe-pointer-passing patterns |
runtime.KeepAlive | Extends object lifetime past a uintptr use |
14. Related references¶
unsafe.Pointerrules: https://pkg.go.dev/unsafe#Pointer- Go spec on
unsafe: https://go.dev/ref/spec#Package_unsafe - "The Go Memory Model": https://go.dev/ref/mem
- cgo +
unsafe: 06-cgo-basics