Unsafe Package
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.
- 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.
Sizeof(x)- Returns size of x in bytesAlignof(x)- Returns alignment of xOffsetof(x.f)- Returns offset of field f in struct xPointer- Generic pointer type
Pointer Operations
Unsafe pointers allow arbitrary pointer arithmetic and type conversions:
Click Run to execute your code
*Tโunsafe.Pointerโ*Uunsafe.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:
Click Run to execute your code
- String โ []byte without copying
- Reinterpret bits (float โ int)
- Struct type conversions
- Interface to concrete type
Memory Operations
Direct memory manipulation for layout and performance:
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:
Click Run to execute your code
- 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:
Click Run to execute your code
- โ Zero-copy string/byte conversions in hot paths
- โ Low-level system programming
- โ Interfacing with C libraries
- โ Performance-critical code (after profiling)
- โ Implementing data structures
- โ 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.