Unsafe: quando e como usar com responsabilidade
1. O pacote unsafe e seus perigos innerentes
O pacote unsafe do Go é uma porta de entrada para operações de baixo nível que violam as garantias de segurança de tipo da linguagem. Enquanto o Go foi projetado para ser seguro, previsível e livre de comportamento indefinido, unsafe permite manipular memória arbitrariamente, ignorando o sistema de tipos.
Os riscos são reais: corrupção de memória, data races, panics misteriosos e comportamento indefinido que pode variar entre versões do compilador. A filosofia do Go sempre priorizou segurança e previsibilidade — unsafe é o contraponto necessário para cenários onde o desempenho bruto ou a interoperabilidade com C são requisitos inegociáveis.
A regra de ouro: use unsafe apenas quando você entende exatamente o layout de memória dos seus dados e não existe alternativa segura viável.
2. Operações fundamentais do pacote unsafe
O pacote expõe três tipos principais:
import "unsafe"
var x int32 = 42
// unsafe.Pointer: ponteiro genérico sem tipo
ptr := unsafe.Pointer(&x)
// uintptr: endereço numérico (cuidado com GC!)
addr := uintptr(ptr)
// Sizeof, Alignof, Offsetof: inspeção de structs
type Exemplo struct {
A bool // 1 byte
B int64 // 8 bytes (alinha a 8)
C string // 16 bytes (pointer + len)
}
fmt.Println(unsafe.Sizeof(Exemplo{})) // 32 (com padding)
fmt.Println(unsafe.Alignof(Exemplo{})) // 8
fmt.Println(unsafe.Offsetof(Exemplo{}.B)) // 8 (após padding)
O perigo do uintptr é que o garbage collector não rastreia endereços numéricos — se o objeto original for movido, o uintptr se torna inválido silenciosamente.
3. Casos de uso legítimos e controlados
Interoperabilidade com C via cgo
/*
#include <stdlib.h>
*/
import "C"
import "unsafe"
func stringParaC(s string) *C.char {
return (*C.char)(unsafe.Pointer(unsafe.StringData(s)))
}
Serialização binária de alto desempenho
type Header struct {
Magic uint32
Version uint16
Flags uint8
_ [1]byte // padding
}
func headerToBytes(h *Header) []byte {
return unsafe.Slice((*byte)(unsafe.Pointer(h)), unsafe.Sizeof(*h))
}
Acesso a campos não exportados (com extrema cautela)
type segredo struct { valor int }
func LerSegredo(s *segredo) int {
return *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(s)) + unsafe.Offsetof(s.valor)))
}
Isso quebra encapsulamento e pode falhar se a struct mudar de versão.
4. Conversões seguras de slice e string
Transformação []byte ↔ string sem cópia
func BytesToString(b []byte) string {
return unsafe.String(unsafe.SliceData(b), len(b))
}
func StringToBytes(s string) []byte {
return unsafe.Slice(unsafe.StringData(s), len(s))
}
Essas funções são zero-allocation, mas perigosas: modificar o []byte resultante de StringToBytes corrompe a string original, quebrando a imutabilidade.
Conversão de []T para []byte
type Pixel struct { R, G, B, A byte }
func PixelsToBytes(pixels []Pixel) []byte {
if len(pixels) == 0 {
return nil
}
return unsafe.Slice((*byte)(unsafe.Pointer(&pixels[0])), len(pixels)*int(unsafe.Sizeof(Pixel{})))
}
Útil para enviar dados para GPU ou sockets, mas requer que o backing array não seja realocado durante o uso.
5. Pattern unsafe + reflect: inspeção e manipulação avançada
import "reflect"
func MakeSlice(typ reflect.Type, len, cap int) interface{} {
sliceType := reflect.SliceOf(typ)
slice := reflect.MakeSlice(sliceType, len, cap)
// Acessa o ponteiro do slice via unsafe
slicePtr := unsafe.Pointer(slice.Pointer())
// Cria um header de slice customizado
type SliceHeader struct {
Data unsafe.Pointer
Len int
Cap int
}
header := (*SliceHeader)(unsafe.Pointer(&slice))
header.Len = len
header.Cap = cap
return slice.Interface()
}
Isso permite criar slices com capacidade maior que o comprimento sem realocar, mas viola contratos internos do runtime.
6. Boas práticas e mitigação de riscos
// Isolar código unsafe em pacote pequeno e bem testado
package bufferpool
import "unsafe"
// PoolBytes converte []byte para string sem cópia
// PRÉ-CONDIÇÃO: o slice não deve ser modificado após a conversão
func PoolBytes(b []byte) string {
return unsafe.String(unsafe.SliceData(b), len(b))
}
Use //go:linkname com moderação para acessar símbolos internos:
//go:linkname runtimeNano runtime.nanotime
func runtimeNano() int64
Documente explicitamente pré-condições e invariantes de memória. Escreva testes que verifiquem comportamento com race detector (-race).
7. Alternativas modernas e quando evitá-lo
encoding/binarypara serialização segura e portável- Generics (Go 1.18+) para algoritmos polimórficos sem
unsafe sync.Poolpara reutilização de buffers sem manipulação direta de ponteirosio.Reader/Writerpara streaming de dados
// Alternativa segura com generics
func BytesTo[T any](b []byte) T {
var v T
buf := bytes.NewReader(b)
binary.Read(buf, binary.LittleEndian, &v)
return v
}
8. Exemplo prático: buffer pool com zero-copy
package main
import (
"fmt"
"sync"
"unsafe"
)
type BufferPool struct {
pool sync.Pool
}
func NewBufferPool(size int) *BufferPool {
return &BufferPool{
pool: sync.Pool{
New: func() interface{} {
buf := make([]byte, 0, size)
return &buf
},
},
}
}
func (bp *BufferPool) Get() []byte {
buf := bp.pool.Get().(*[]byte)
return *buf
}
func (bp *BufferPool) Put(buf []byte) {
buf = buf[:0]
bp.pool.Put(&buf)
}
// Versão unsafe: converte para string sem cópia
func (bp *BufferPool) GetString() string {
buf := bp.Get()
return unsafe.String(unsafe.SliceData(buf), len(buf))
}
func main() {
pool := NewBufferPool(1024)
buf := pool.Get()
buf = append(buf, "hello"...)
s := pool.GetString() // zero-copy, mas perigoso!
fmt.Println(s)
pool.Put(buf)
}
Benchmark comparativo:
func BenchmarkSafe(b *testing.B) {
pool := NewBufferPool(1024)
for i := 0; i < b.N; i++ {
buf := pool.Get()
buf = append(buf, "data"...)
s := string(buf) // cópia
_ = s
pool.Put(buf)
}
}
func BenchmarkUnsafe(b *testing.B) {
pool := NewBufferPool(1024)
for i := 0; i < b.N; i++ {
buf := pool.Get()
buf = append(buf, "data"...)
s := unsafe.String(unsafe.SliceData(buf), len(buf))
_ = s
pool.Put(buf)
}
}
A versão unsafe elimina a alocação de string, mas exige que o buffer não seja modificado após a conversão. O ganho de performance (~30-50% em benchmarks) vem com o custo de complexidade e risco de bugs sutis.
Referências
- Documentação oficial do pacote unsafe — Referência completa de todas as funções e tipos do pacote unsafe no Go
- Go Blog: The Laws of Reflection — Artigo fundamental sobre reflection e sua relação com unsafe.Pointer
- Go 1.17 Release Notes: unsafe.Add e unsafe.Slice — Novas funções adicionadas ao pacote unsafe na versão 1.17
- Understanding Go's unsafe.Pointer — Tutorial prático sobre os usos e riscos de unsafe.Pointer
- Go Memory Model — Documentação oficial sobre garantias de memória e sincronização em Go
- Dave Cheney: High Performance Go — Palestra e artigo sobre otimizações em Go, incluindo uso responsável de unsafe
- Uber Go Style Guide: Unsafe — Guia de estilo da Uber com recomendações sobre uso de unsafe em produção