Using the unsafe Package in Go
Limitations of Go Pointers
Go language imposes strict restrictions on pointer operations to ensure memory safety and type safety:
- Go pointers cannot perform arithmetic operations - Unlike C, you cannot add or subtract from pointers
- Pointers of different types cannot be converted - Type casting between pointer types is prohibited
- Pointers of different types cannot be compared using == or != - Direct comparison of different pointer types is impossible
- Pointer variables of different types cannot be assigned to each other - Prevents accidental type confusion
Core Capabilities Provided by unsafe Package
The unsafe package bypasses these restrictions, providing two crucial capabilities:
- Any type of pointer can be converted to and from unsafe.Pointer
- uintptr type and unsafe.Pointer can be converted to each other
Important Note:
uintptrdoes not carry pointer semantics, meaning objects pointed to byuintptrcan be garbage collected without mercy. Whereasunsafe.Pointerhas pointer semantics and can protect the objects it points to from garbage collection while they are “in use.”
Practical Application Scenarios of unsafe Package
1. Bypassing Private Member Restrictions
The unsafe package allows accessing and modifying private fields by bypassing access restrictions.
Definition in package A:
package A
type A struct {
name string
age int
mark bool
}
Using unsafe to access private fields in main program:
package main
import (
"A"
"fmt"
"unsafe"
)
func main() {
a := A.A{}
fmt.Println(a) // Output initial values
// Access private fields through unsafe operations
name := (*string)(unsafe.Pointer(&a))
age := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&a)) + unsafe.Sizeof(string(""))))
mark := (*bool)(unsafe.Pointer(uintptr(unsafe.Pointer(&a)) + unsafe.Sizeof(string("")) + unsafe.Sizeof(0)))
*name = "A"
*age = 20
*mark = false
// Output: {A 20 false}
fmt.Println(a)
}
2. Rapid Private Property Manipulation Through Structure Fabrication
Using the built-in slice type as an example, manipulate internal slice data by constructing a struct with identical memory layout.
package main
import (
"fmt"
"unsafe"
)
// Construct a struct with the same memory layout as slice
type slice struct {
arrptr unsafe.Pointer
len int
cap int
}
func main() {
a := []int{1, 2}
a = append(a, 3)
// Directly manipulate slice's length field
length := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&a)) + unsafe.Sizeof(unsafe.Pointer(&a))))
capacity := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&a)) + unsafe.Sizeof(unsafe.Pointer(&a)) + unsafe.Sizeof(int(0))))
fmt.Println(*length, *capacity) // Output current length and capacity
*length = *length + 1 // Modify slice length
fmt.Println(a, *length, *capacity)
// Access slice internal array through type conversion
s := *(*slice)(unsafe.Pointer(&a))
fmt.Println(s)
// Access elements of slice's underlying array
fmt.Printf("[%d, %d, %d, %d]\n",
*(*int)(s.arrptr),
*(*int)(unsafe.Pointer(uintptr(s.arrptr) + unsafe.Sizeof(int(0)))),
*(*int)(unsafe.Pointer(uintptr(s.arrptr) + 2*unsafe.Sizeof(int(0)))),
*(*int)(unsafe.Pointer(uintptr(s.arrptr) + 3*unsafe.Sizeof(int(0)))),
)
}