Unsafe Code

Updated

July 30, 2026

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 pointer

Common 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 offset

String 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

  1. Avoid if possible - Use safer alternatives first
  2. Document thoroughly - Explain why unsafe is needed
  3. Isolate usage - Keep in small, well-tested functions
  4. 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.