String and []byte Internals
String and []byte Internals
Overview
Strings are immutable ptr+len headers over bytes (usually UTF-8). []byte is ptr+len+cap. Converting between them often allocates — a hot-path footgun.
Diagram: Headers
diagram:
S[string ptr+len] --> Imm[immutable bytes]
B[slice ptr+len+cap] --> Mut[mutable]
Conv[string b / byte s] --> Copy[usually copy]
Headers
string: { data *byte, len int } // immutable
[]byte: { data *byte, len int, cap int } // mutable
s := "go"
b := []byte(s) // copy
s2 := string(b) // copyCompiler sometimes proves conversions are temporary and elides copies (e.g. map lookup m[string(b)] optimizations in modern Go) — measure, don’t assume.
Range and Indexing
for i, r := range s { /* r is rune; i is byte index */ }
s[i] // byte, not runeunsafe Conversions (Advanced)
// historical patterns; prefer safer APIs / newer runtime helpers when available
// wrong lifetime → memory corruptionOnly with full understanding of immutability: never mutate a []byte view of a string.
Builder and Buffer
strings.Builder— string assemblybytes.Buffer— alsoio.Writerbytealgruntime helpers powerstrings/bytes(assembly on some arch)
Experiment
go test -bench=. -benchmempackage conv_test
import "testing"
var sink string
func BenchmarkByteToString(b *testing.B) {
raw := []byte("authorization: bearer ...")
for i := 0; i < b.N; i++ {
sink = string(raw)
}
}
func BenchmarkMapKey(b *testing.B) {
m := map[string]int{"authorization: bearer ...": 1}
raw := []byte("authorization: bearer ...")
for i := 0; i < b.N; i++ {
_ = m[string(raw)]
}
}What to notice: Map key optimization may reduce allocs vs naive assign to sink.
Try next: Profile a JSON logger converting large []byte bodies to string.