Web Analytics

Unsafe Package

Advanced ~40 min read

The unsafe package provides low-level operations that bypass Go's type safety. While powerful for optimization and system programming, it requires extreme care. Learn when to use unsafe, how to use it correctly, and understand the risks involved.

โš ๏ธ Critical Warning:
  • Unsafe code bypasses Go's type safety
  • Can cause crashes, data corruption, and security issues
  • Not protected by Go's compatibility promise
  • Should be used only when absolutely necessary
  • Requires thorough testing and documentation

What is the Unsafe Package?

The unsafe package provides operations that step around Go's type safety. It's used for low-level programming, performance optimization, and interfacing with non-Go code.

Unsafe Package Functions:
  • Sizeof(x) - Returns size of x in bytes
  • Alignof(x) - Returns alignment of x
  • Offsetof(x.f) - Returns offset of field f in struct x
  • Pointer - Generic pointer type

Pointer Operations

Unsafe pointers allow arbitrary pointer arithmetic and type conversions:

Output
Click Run to execute your code
Pointer Conversion Rules:
  • *T โ†’ unsafe.Pointer โ†’ *U
  • unsafe.Pointer โ†’ uintptr (for arithmetic)
  • uintptr โ†’ unsafe.Pointer (back to pointer)
  • Never store uintptr - GC won't track it

Type Conversions

Unsafe allows zero-copy conversions between compatible types:

Output
Click Run to execute your code
Zero-Copy Conversions:
  • String โ†” []byte without copying
  • Reinterpret bits (float โ†” int)
  • Struct type conversions
  • Interface to concrete type

Memory Operations

Direct memory manipulation for layout and performance:

Output
Click Run to execute your code
Type Size (bytes) Alignment
bool 1 1
int8, uint8 1 1
int16, uint16 2 2
int32, uint32, float32 4 4
int64, uint64, float64 8 8
pointer 8 (64-bit) 8

Struct Field Access

Access struct fields by offset for optimization:

Output
Click Run to execute your code
Struct Packing Tips:
  • Order fields by size (largest first)
  • Group related fields together
  • Use Sizeof() to check total size
  • Use Offsetof() to verify layout
  • Consider cache line alignment (64 bytes)

Performance Optimization

Benchmark unsafe vs safe operations:

Output
Click Run to execute your code
When to Use Unsafe:
  • โœ“ Zero-copy string/byte conversions in hot paths
  • โœ“ Low-level system programming
  • โœ“ Interfacing with C libraries
  • โœ“ Performance-critical code (after profiling)
  • โœ“ Implementing data structures
When NOT to Use Unsafe:
  • โŒ Regular application code
  • โŒ Without profiling first
  • โŒ In public APIs
  • โŒ When safe alternatives exist
  • โŒ Without thorough testing

Common Mistakes

1. Storing uintptr instead of unsafe.Pointer

// โŒ Wrong - GC won't track uintptr
type Bad struct {
    ptr uintptr // Can become invalid!
}

// โœ… Correct - GC tracks unsafe.Pointer
type Good struct {
    ptr unsafe.Pointer
}

2. Modifying string bytes

// โŒ Wrong - strings are immutable
s := "hello"
bytes := *(*[]byte)(unsafe.Pointer(&s))
bytes[0] = 'H' // Undefined behavior!

// โœ… Correct - create new string
bytes := []byte(s)
bytes[0] = 'H'
s = string(bytes)

3. Ignoring alignment requirements

// โŒ Wrong - may cause crash on some platforms
bytes := []byte{1, 2, 3, 4, 5, 6, 7, 8}
ptr := (*int64)(unsafe.Pointer(&bytes[1])) // Misaligned!

// โœ… Correct - ensure proper alignment
ptr := (*int64)(unsafe.Pointer(&bytes[0])) // Aligned

Exercise: Zero-Copy String Builder

Task: Build a string builder using unsafe for zero-copy operations.

Requirements:

  • Append strings without copying
  • Convert to final string efficiently
  • Handle edge cases safely
  • Benchmark against strings.Builder
Show Solution
package main

import (
    "fmt"
    "strings"
    "time"
    "unsafe"
)

// StringHeader for unsafe string operations
type StringHeader struct {
    Data uintptr
    Len  int
}

// SliceHeader for unsafe slice operations
type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}

// UnsafeBuilder builds strings using unsafe operations
type UnsafeBuilder struct {
    buf []byte
}

// NewUnsafeBuilder creates a new builder
func NewUnsafeBuilder(capacity int) *UnsafeBuilder {
    return &UnsafeBuilder{
        buf: make([]byte, 0, capacity),
    }
}

// Append adds a string without copying
func (b *UnsafeBuilder) Append(s string) {
    // Convert string to []byte without copying
    strHeader := (*StringHeader)(unsafe.Pointer(&s))
    bytes := *(*[]byte)(unsafe.Pointer(&SliceHeader{
        Data: strHeader.Data,
        Len:  strHeader.Len,
        Cap:  strHeader.Len,
    }))
    
    // Append to buffer
    b.buf = append(b.buf, bytes...)
}

// String returns the final string
func (b *UnsafeBuilder) String() string {
    // Convert []byte to string without copying
    sliceHeader := (*SliceHeader)(unsafe.Pointer(&b.buf))
    return *(*string)(unsafe.Pointer(&StringHeader{
        Data: sliceHeader.Data,
        Len:  sliceHeader.Len,
    }))
}

func main() {
    // Test correctness
    fmt.Println("Testing UnsafeBuilder:")
    builder := NewUnsafeBuilder(100)
    builder.Append("Hello, ")
    builder.Append("unsafe ")
    builder.Append("world!")
    result := builder.String()
    fmt.Printf("Result: %s\n\n", result)
    
    // Benchmark
    iterations := 100000
    parts := []string{"Hello, ", "this ", "is ", "a ", "test!"}
    
    // strings.Builder
    start := time.Now()
    for i := 0; i < iterations; i++ {
        var sb strings.Builder
        for _, p := range parts {
            sb.WriteString(p)
        }
        _ = sb.String()
    }
    safeDuration := time.Since(start)
    
    // UnsafeBuilder
    start = time.Now()
    for i := 0; i < iterations; i++ {
        ub := NewUnsafeBuilder(50)
        for _, p := range parts {
            ub.Append(p)
        }
        _ = ub.String()
    }
    unsafeDuration := time.Since(start)
    
    fmt.Println("Benchmark results:")
    fmt.Printf("strings.Builder: %v\n", safeDuration)
    fmt.Printf("UnsafeBuilder:   %v\n", unsafeDuration)
    fmt.Printf("Speedup:         %.2fx\n", float64(safeDuration)/float64(unsafeDuration))
}

Summary

  • unsafe package bypasses Go's type safety
  • Sizeof() returns size in bytes
  • Alignof() returns alignment requirement
  • Offsetof() returns field offset in struct
  • unsafe.Pointer is a generic pointer type
  • uintptr for pointer arithmetic (temporary only)
  • Zero-copy conversions possible
  • Struct packing reduces memory usage
  • Performance gains in critical paths
  • Use sparingly and document thoroughly

What's Next?

You've learned the unsafe package! While powerful, remember that with great power comes great responsibility. Next, you'll explore compiler and linker flags to optimize your Go builds, control compilation, and fine-tune performance.