Breaking Type Safety
In Go, you cannot cast an *int pointer to a *float64 pointer. The compiler forbids it because the bit representations are different — an int value of 1 and a float64 value of 1.0 have completely different byte layouts in memory. Allowing arbitrary casts would silently corrupt data.
The unsafe package lets you bypass this protection entirely. unsafe.Pointer is a special pointer type that can be cast to any other pointer type, and to uintptr (an integer large enough to hold a memory address).
The three core operations:
import "unsafe"
// 1. Convert any pointer to unsafe.Pointer
p := unsafe.Pointer(&myVar)
// 2. Convert unsafe.Pointer to a different pointer type
q := (*int64)(p)
// 3. Convert to uintptr for pointer arithmetic, then back
addr := uintptr(p) + 8 // move 8 bytes forward in memory
r := (*int64)(unsafe.Pointer(addr))
Why would you ever do this?
- Zero-copy string/byte-slice conversion: Converting a
stringto[]bytenormally allocates a new backing array and copies the data. Withunsafe, you can reinterpret the same memory as both types, avoiding the allocation entirely. This matters in high-throughput parsers processing millions of messages per second. - System calls: The
syscallpackage usesunsafe.Pointerto pass Go-managed memory buffers directly to the OS kernel, which expects raw addresses. - Struct field offsets:
unsafe.Offsetof(T.Field)returns the byte offset of a field within a struct, which is used by low-level serialization code to read/write fields without reflection.
The danger is real: If you calculate a pointer address incorrectly and dereference it, the Go runtime cannot save you. There is no bounds check, no garbage collector protection, no recover(). You get a segmentation fault and your program dies instantly. Worse, you might read adjacent memory and get silently wrong data — a bug that is nearly impossible to reproduce.
A common interview question is: "How would you convert a []byte to a string without allocating?" The unsafe answer is to use unsafe.Pointer to reinterpret the slice header as a string header (both are {ptr, len} internally). The correct follow-up is to immediately note the constraint: the byte slice must not be modified after the conversion, because strings in Go are immutable by contract — mutating the underlying bytes would violate that contract and cause undefined behavior. Demonstrating you know both the technique and its invariants is what separates a strong answer from a dangerous one.