Unsafe Code
Overview
The unsafe package bypasses Go’s type safety. Use only when absolutely necessary for performance or interoperability.
unsafe.Pointer
import "unsafe"
// Convert between pointer types
var x int = 42
p := unsafe.Pointer(&x)
fp := (*float64)(p) // Reinterpret as float64 pointerCommon Uses
Memory Layout
type Data struct {
a int32
b int64
}
fmt.Println(unsafe.Sizeof(Data{})) // Size in bytes
fmt.Println(unsafe.Alignof(Data{})) // Alignment
fmt.Println(unsafe.Offsetof(Data{}.b)) // Field offsetString to Bytes (Zero-Copy)
func stringToBytes(s string) []byte {
return unsafe.Slice(unsafe.StringData(s), len(s))
}Accessing Unexported Fields
// Not recommended, but possible
type hidden struct {
secret int
}
h := hidden{secret: 42}
p := unsafe.Pointer(&h)
secretPtr := (*int)(p)
fmt.Println(*secretPtr)Dangers
- Not portable across platforms
- May break with Go versions
- Undefined behavior if misused
- Bypasses garbage collector
Guidelines
- Avoid if possible - Use safer alternatives first
- Document thoroughly - Explain why unsafe is needed
- Isolate usage - Keep in small, well-tested functions
- Test extensively - Including on target platforms
Summary
| Function | Purpose |
|---|---|
unsafe.Pointer |
Generic pointer type |
unsafe.Sizeof |
Size in bytes |
unsafe.Alignof |
Alignment requirement |
unsafe.Offsetof |
Field offset |
Worked example
Layout inspection: padding vs field reordering.
Save as main.go. Then:
go mod init example
go run .package main
import (
"fmt"
"unsafe"
)
type Bad struct {
A bool
B int64
C bool
}
type Good struct {
B int64
A bool
C bool
}
func main() {
fmt.Println("sizeof Bad:", unsafe.Sizeof(Bad{}))
fmt.Println("sizeof Good:", unsafe.Sizeof(Good{}))
fmt.Println("offset Bad.B:", unsafe.Offsetof(Bad{}.B))
fmt.Println("offset Good.B:", unsafe.Offsetof(Good{}.B))
}Expected output: (typical amd64/arm64)
sizeof Bad: 24
sizeof Good: 16
offset Bad.B: 8
offset Good.B: 0
More examples
Safe-ish string data inspection (do not mutate the slice).
package main
import (
"fmt"
"unsafe"
)
func main() {
s := "gopher"
b := unsafe.Slice(unsafe.StringData(s), len(s))
fmt.Println("len:", len(b), "as string:", string(b))
// b[0] = 'x' // NEVER: would corrupt immutable string data
}Expected output:
len: 6 as string: gopher
Runnable example
Save as main.go. Then:
go mod init example
go run .package main
import (
"fmt"
"unsafe"
)
type Data struct {
A int32
B int64
}
func main() {
d := Data{A: 1, B: 2}
fmt.Println("sizeof Data:", unsafe.Sizeof(d))
fmt.Println("alignof Data:", unsafe.Alignof(d))
fmt.Println("offsetof B:", unsafe.Offsetof(d.B))
// Safe-ish modern helpers: inspect string backing without copy.
// The returned slice must not be mutated; string data is immutable.
s := "hello"
ptr := unsafe.StringData(s)
b := unsafe.Slice(ptr, len(s))
fmt.Println("string bytes len:", len(b), "first:", string(b[0]))
// Reinterpret an int64's bits as float64 (IEEE754 bit pattern demo)
var n uint64 = 0x3FF0000000000000 // 1.0 in float64
f := *(*float64)(unsafe.Pointer(&n))
fmt.Println("bits as float64:", f)
}Expected output: (sizes may vary by arch; on typical amd64/arm64)
sizeof Data: 16
alignof Data: 8
offsetof B: 8
string bytes len: 5 first: h
bits as float64: 1
What to notice: Padding makes Data larger than 4+8 raw bytes. unsafe skips type checks—misaligned or out-of-lifetime pointers are undefined behavior. Prefer binary, math, and normal conversions unless you measure a need.
Try next: Print Sizeof for struct { a bool; b int64 } vs reordered fields. Never store unsafe.Pointer derived from a stack variable past its lifetime.