Unsafe Pointer — Specification¶
Focus: Precise reference for the
unsafe.Pointertype and its companion APIs. Covers every exported identifier in theunsafepackage, the exact wording of the six legal conversion patterns, the version history (Go 1.17Add/Slice, Go 1.20String/StringData/SliceData), and the deprecation timeline forreflect.SliceHeader/reflect.StringHeader. This file is the canonical lookup; the audience files (junior/middle/senior/professional) explain why and when.Sources: -
unsafepackage docs: https://pkg.go.dev/unsafe - Go language spec, "Package unsafe": https://go.dev/ref/spec#Package_unsafe -unsafeptranalyzer: https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unsafeptr -src/unsafe/unsafe.gosource (since the package is mostly compiler-implemented, the source is a doc page) - Go release notes: https://go.dev/doc/go1.17, https://go.dev/doc/go1.20, https://go.dev/doc/go1.21
1. The unsafe package surface¶
The complete exported API as of Go 1.22:
package unsafe
// Types
type ArbitraryType int // placeholder used in doc signatures
type IntegerType int // placeholder for any integer type
type Pointer *ArbitraryType
// Sizing primitives (compile-time constants)
func Alignof(x ArbitraryType) uintptr
func Offsetof(x ArbitraryType) uintptr // x must be a selector expression: var.field
func Sizeof(x ArbitraryType) uintptr
// Pointer arithmetic (Go 1.17+)
func Add(ptr Pointer, len IntegerType) Pointer
// Slice / string construction (Go 1.17 / 1.20)
func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType
func SliceData(slice []ArbitraryType) *ArbitraryType // Go 1.20+
func String(ptr *byte, len IntegerType) string // Go 1.20+
func StringData(str string) *byte // Go 1.20+
The package has no non-exported runtime; all functions are compiler intrinsics. ArbitraryType is a stand-in in the docs (not a real type) — actual call sites use the concrete types.
2. The unsafe.Pointer type¶
2.1 Definition¶
Pointer is the generic pointer type. It is the only Go type with these properties:
- A value of any pointer type can be converted to a
Pointer. - A
Pointercan be converted to a value of any pointer type. - A
uintptrcan be converted to aPointer. - A
Pointercan be converted to auintptr.
These four conversions are the syntactic permissions. The semantic rules — what makes a use valid vs undefined — are the six patterns in §4.
2.2 GC treatment¶
Pointer values are tracked by the garbage collector exactly like *T:
- An
unsafe.Pointerfield in a struct contributes a1bit to the type's pointer map. - The GC follows the pointer during tracing.
- The pointed-to object is kept alive.
By contrast, uintptr is treated as a scalar (bit = 0), is not followed, and does not keep memory alive.
3. Companion functions¶
3.1 unsafe.Sizeof¶
Returns the size in bytes of the type of x, including padding. Compile-time constant. The expression x is not evaluated.
| Expression | Result on 64-bit |
|---|---|
Sizeof(int(0)) | 8 |
Sizeof(int32(0)) | 4 |
Sizeof("hello") | 16 (string header) |
Sizeof([]int{}) | 24 (slice header) |
Sizeof(map[int]int{}) | 8 (map pointer) |
Sizeof(struct{ a int8; b int64 }{}) | 16 (1 + 7 padding + 8) |
3.2 unsafe.Alignof¶
Returns the alignment requirement of the type of x. Compile-time constant.
| Type | Alignof on amd64 |
|---|---|
int8, bool, byte | 1 |
int16, uint16 | 2 |
int32, uint32, float32 | 4 |
int64, uint64, float64, complex64, int, uintptr, pointer | 8 |
complex128 | 8 |
| Struct | max of field alignments |
3.3 unsafe.Offsetof¶
The argument must be a selector expression s.f where s is a struct value. Returns the byte offset of f within s's type. Compile-time constant.
Embedded fields are reached by their name; promoted fields require the embedded type's name. Offsetof(T{}.embedded.field) works.
3.4 unsafe.Add (Go 1.17+)¶
Returns ptr + len interpreted as a byte offset. The len argument can be any integer type (it's auto-converted to uintptr).
| Equivalent expression | Note |
|---|---|
unsafe.Pointer(uintptr(p) + uintptr(n)) | The pre-1.17 form |
The function is safer than the manual form because the conversion happens internally to the runtime/compiler — there's no uintptr exposed at the Go source level for the GC to miss.
Add does not bounds-check. unsafe.Add(p, 100) where p points to a 16-byte object produces an address into other memory; reading from it is undefined.
3.5 unsafe.Slice (Go 1.17+)¶
Returns a slice whose underlying array starts at ptr and has length and capacity equal to len.
| Condition | Result |
|---|---|
ptr == nil && len == 0 | nil |
ptr == nil && len != 0 | panic |
len < 0 | panic |
len * sizeof(T) overflows uintptr | panic |
| Otherwise | slice of len elements |
The panics are runtime panics, not compile-time checks. Pre-1.22 some edge cases were silently undefined; Go 1.22 tightened the checks.
3.6 unsafe.SliceData (Go 1.20+)¶
Returns a pointer to the underlying array of slice.
| Condition | Result |
|---|---|
cap(slice) > 0 | &slice[:1][0] (the first element of the underlying array) |
slice == nil | nil |
slice != nil && cap(slice) == 0 | non-nil; address is implementation-defined |
Replaces the pre-1.20 (*reflect.SliceHeader)(unsafe.Pointer(&s)).Data.
3.7 unsafe.String (Go 1.20+)¶
Returns a string whose underlying bytes start at ptr and have length len.
| Condition | Result |
|---|---|
len == 0 | "" (regardless of ptr) |
ptr == nil && len != 0 | panic |
len < 0 | panic |
| Otherwise | string aliasing the bytes at ptr |
The caller is responsible for ensuring the bytes do not change. Strings are by-spec immutable; if the underlying bytes change, the program is in an inconsistent state.
3.8 unsafe.StringData (Go 1.20+)¶
Returns a pointer to the first byte of str's underlying bytes. For an empty string the result is implementation-defined but always non-nil for non-empty strings.
Replaces the pre-1.20 (*reflect.StringHeader)(unsafe.Pointer(&s)).Data.
4. The six legal conversion patterns¶
Quoted (paraphrased) from pkg.go.dev/unsafe#Pointer. Any use of unsafe.Pointer not matching one of these patterns is undefined behaviour.
Pattern 1: Conversion of a *T1 to *T2¶
Provided that T2 is no larger than T1 and that the two share an equivalent memory layout, this conversion allows reinterpreting data of one type as data of another.
Practical reading: cast *int64 to *[8]byte for byte access; cast a struct pointer to *byte to compute a checksum. Sizes need not match exactly but the second must not read past the first.
Pattern 2: Conversion of a Pointer to a uintptr (but not back)¶
Converting a Pointer to a uintptr produces the memory address of the value pointed at, as an integer.
The uintptr so produced can be printed, logged, compared, but must not be stored and converted back later. Storing it and converting back is Pattern 3 (constrained) or undefined (if the round trip spans statements).
Pattern 3: Conversion of a Pointer to a uintptr and back, with arithmetic¶
If p points into an allocated object, it can be advanced through the object by conversion to uintptr, addition of an offset, and conversion back to Pointer.
The conversion-to-uintptr + arithmetic + conversion-back must be one expression:
// Legal
p2 := unsafe.Pointer(uintptr(p) + offset)
// Illegal — the uintptr is stored
u := uintptr(p)
p2 := unsafe.Pointer(u + offset)
The doc states valid arithmetic forms: subtraction of two pointers, addition of unsafe.Sizeof, unsafe.Offsetof, or unsafe.Alignof constants, and conversion of the result back via unsafe.Pointer. Anything else (multiplying, masking the low bits, etc.) is not explicitly covered.
unsafe.Add (Go 1.17+) is the modern equivalent.
Pattern 4: Conversion of a Pointer to a uintptr when calling syscall.Syscall¶
The Syscall functions in package syscall pass their uintptr arguments directly to the operating system, which may then, depending on the details of the call, reinterpret some of them as pointers. ... a uintptr conversion of a Pointer argument must appear in the call expression itself.
Specifically valid:
The compiler recognizes this exact pattern in calls to functions whose names start with syscall.Syscall, syscall.SyscallN, syscall.RawSyscall, etc., and keeps the pointer live for the duration of the call. The recognition is hardcoded — wrapper functions don't inherit it.
Pattern 5: Conversion of the result of reflect.Value.Pointer or reflect.Value.UnsafeAddr from uintptr to Pointer¶
Package reflect's Value methods named Pointer and UnsafeAddr return type uintptr instead of unsafe.Pointer to keep callers from changing the result to an arbitrary type without first importing unsafe. However, this means that the result is fragile and must be converted to Pointer immediately after making the call, in the same expression.
Legal:
Illegal:
Go 1.18 added reflect.Value.UnsafePointer() returning unsafe.Pointer directly — prefer it.
Pattern 6: Conversion of a reflect.SliceHeader or reflect.StringHeader Data field to or from Pointer¶
Reading or modifying the slice/string header via reflect.SliceHeader / reflect.StringHeader's Data field is permitted only by converting it to or from unsafe.Pointer.
var s string = "hello"
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
// sh.Data is a uintptr — must be converted via unsafe.Pointer for use as a pointer
p := unsafe.Pointer(sh.Data)
Deprecated as of Go 1.20. Use unsafe.SliceData / unsafe.StringData instead. See §6 below.
5. The unsafeptr analyzer¶
The unsafeptr analyzer (golang.org/x/tools/go/analysis/passes/unsafeptr) is included in go vet's default analyzer set.
| Pattern detected | Reported |
|---|---|
unsafe.Pointer(x) where x is uintptr but did not originate from uintptr(unsafe.Pointer(...)) in this expression | Yes |
unsafe.Pointer(uintptr(p) + N) where N is unsafe.Sizeof/Alignof/Offsetof or a multiple thereof | Allowed |
unsafe.Pointer(rv.Pointer()) where rv is reflect.Value | Allowed (Pattern 5) |
The analyzer is syntactic: it pattern-matches AST shapes and does not perform data-flow or alias analysis. Bugs that hide behind a wrapper function or behind dataflow that the analyzer doesn't traverse are not flagged.
Run explicitly:
Disable (not recommended):
6. Version history¶
| Go version | Change |
|---|---|
| 1.0 | unsafe.Pointer, Sizeof, Alignof, Offsetof |
| 1.0 | Six legal patterns documented |
| 1.5 | unsafeptr analyzer added to go vet |
| 1.8 | unsafeptr strengthened to flag arithmetic-without-keep-alive |
| 1.14 | -d=checkptr runtime check added (alignment, bounds) |
| 1.17 | unsafe.Add(p, n) and unsafe.Slice(p, n) added; replace manual uintptr arithmetic |
| 1.18 | reflect.Value.UnsafePointer() added; obsoletes Value.Pointer() for the Pattern 5 case |
| 1.20 | unsafe.String(p, n), unsafe.StringData(s), unsafe.SliceData(s) added |
| 1.20 | reflect.SliceHeader and reflect.StringHeader deprecated |
| 1.21 | runtime.Pinner added; supersedes cgo's C.malloc-then-copy workaround |
| 1.22 | unsafe.Slice and unsafe.String panic on overflow/negative length (was undefined) |
7. The runtime.KeepAlive companion¶
Marks x as reachable at the call site. No-op at runtime; purely a compile-time signal to escape analysis.
Used to extend a variable's lifetime past its apparent last use, typically when the address has been passed to a syscall or cgo call via uintptr (where the compiler can't see the dependency).
n, _, _ := syscall.Syscall(SYS_WRITE, uintptr(fd), uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf)))
runtime.KeepAlive(buf)
Without KeepAlive, the compiler could conclude buf is dead after the uintptr(unsafe.Pointer(&buf[0])) expression and free it before the syscall returns.
For syscall.Syscall itself, the keepalive is automatic; but for any wrapper, you must add it.
8. The runtime.Pinner API (Go 1.21+)¶
package runtime
type Pinner struct { /* opaque */ }
func (p *Pinner) Pin(pointer any)
func (p *Pinner) Unpin()
Pin marks an object so it cannot be moved or collected. Unpin releases all pins for the receiver.
Use in cgo to pass mutable Go memory to a C function that retains the pointer (e.g., for an async callback). Pre-1.21, this required copying to C.malloc-allocated memory.
var pinner runtime.Pinner
defer pinner.Unpin()
pinner.Pin(&buf[0])
C.someAsyncFunc(unsafe.Pointer(&buf[0]))
The pin lasts until Unpin. Multiple pins on the same object are reference-counted by the runtime.
9. Build-time toggles¶
| Flag | Effect |
|---|---|
go vet -unsafeptr | Run only the unsafeptr analyzer |
go build -gcflags="all=-d=checkptr" | Insert runtime checks for alignment, bounds, validity |
go test -race | Enable race detector + checkptr |
go build -ldflags="-s -w" | Strip symbols; unrelated to safety |
For CI on unsafe-touching packages:
This combination catches the vast majority of unsafe.Pointer misuse in tests. See professional.md §7 for the full CI recipe.
10. Deprecated and grandfathered APIs¶
| API | Status | Replacement |
|---|---|---|
reflect.SliceHeader | Deprecated 1.20 | unsafe.Slice, unsafe.SliceData |
reflect.StringHeader | Deprecated 1.20 | unsafe.String, unsafe.StringData |
reflect.Value.Pointer() returning uintptr | Soft-deprecated 1.18 | reflect.Value.UnsafePointer() |
reflect.Value.UnsafeAddr() returning uintptr | Still supported | Same — but consider Addr().UnsafePointer() |
Manual uintptr-arithmetic for offsetting | Still supported | unsafe.Add |
unsafe.Pointer(uintptr(p) + n) for slice construction | Still supported | unsafe.Slice(p, n) |
staticcheck rule SA1019 flags reflect.SliceHeader / StringHeader usage. Treat warnings as errors in new code.
11. Interaction with cgo¶
cgo adds its own rules on top of unsafe.Pointer:
| Direction | Rule |
|---|---|
Go → C: passing *T (T has no pointer fields) | OK; no special action needed |
Go → C: passing *T (T contains Go pointer fields) | C must not store the pointer; or use runtime.Pinner |
Go → C: passing unsafe.Pointer(&slice[0]) | OK for the call duration; KeepAlive(slice) after |
| C → Go: receiving C pointer | Treat as unsafe.Pointer; do not feed to GC-tracked Go pointer fields |
C → Go: C.GoString, C.GoBytes | Copy from C memory into Go memory; safe |
C → Go: unsafe.Slice((*byte)(cmem), n) | Alias of C memory; valid until C.free is called |
See cgo's pointer-passing docs for the canonical rules.
12. Comparison to *T and uintptr¶
| Property | *T | unsafe.Pointer | uintptr |
|---|---|---|---|
| Type-safe | Yes | No | N/A (integer) |
| GC-tracked | Yes | Yes | No |
Convertible to/from uintptr | No | Yes | N/A |
| Convertible to other pointer types | No (except via unsafe.Pointer) | Yes | No |
| Survives stack-grow | Yes | Yes | No |
| Vet-checked usage rules | Standard | Six patterns | N/A |
Sizeof on amd64 | 8 | 8 | 8 |
The three are size-equal at runtime; the type system difference is the entire point.
13. Quick-reference: legal vs illegal¶
| Code | Legal? | Pattern |
|---|---|---|
p := unsafe.Pointer(&x) | Yes | — (constructor) |
q := (*int64)(unsafe.Pointer(&x)) | Yes (if sizes match) | 1 |
u := uintptr(unsafe.Pointer(&x)); log.Print(u) | Yes (read-only use of uintptr) | 2 (one-way) |
p2 := unsafe.Pointer(uintptr(p) + 8) | Yes (one expression) | 3 |
u := uintptr(p); p2 := unsafe.Pointer(u + 8) | No | — |
p2 := unsafe.Add(p, 8) | Yes | 3 (modern) |
syscall.Syscall(N, 0, uintptr(unsafe.Pointer(p)), 0) | Yes | 4 |
mySyscall(N, 0, uintptr(unsafe.Pointer(p)), 0) (custom) | No (without KeepAlive) | — |
unsafe.Pointer(rv.UnsafeAddr()) | Yes | 5 |
u := rv.UnsafeAddr(); unsafe.Pointer(u) | No | — |
sh := (*reflect.SliceHeader)(unsafe.Pointer(&b)) | Yes (deprecated) | 6 |
unsafe.String(unsafe.SliceData(b), len(b)) | Yes (modern equivalent) | 1 + 3 |
14. Pointer alignment table (amd64 / arm64)¶
| Type | Alignof | Notes |
|---|---|---|
byte, bool, int8 | 1 | |
int16, uint16 | 2 | |
int32, uint32, float32, rune | 4 | |
int64, uint64, float64, int, uintptr, Pointer, *T | 8 | |
complex128 | 8 | |
string header | 8 | |
[]T header | 8 | |
map[K]V | 8 | |
interface{} | 8 | |
| Struct | max of fields' Alignof |
A *T reads or writes require the address to be Alignof(T)-aligned. On amd64, misalignment is slow but usually works; on arm64 it may fault with SIGBUS.
15. Related references¶
unsafedoc page (with rules): https://pkg.go.dev/unsafe#Pointer- Language spec on
unsafe: https://go.dev/ref/spec#Package_unsafe unsafeptranalyzer: https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unsafeptrruntime.Pinner: https://pkg.go.dev/runtime#Pinnerruntime.KeepAlive: https://pkg.go.dev/runtime#KeepAlive-d=checkptrissue: https://github.com/golang/go/issues/22218unsafe.Addproposal: https://github.com/golang/go/issues/40481unsafe.Sliceproposal: https://github.com/golang/go/issues/19367unsafe.String/Dataproposal: https://github.com/golang/go/issues/53003- Related: string-internals
- Related: slice-header-internals
- Related: unsafe-package