String Header: unsafe.String, unsafe.SliceData, Zero-Copy Casting
- Last updated
- Prerequisites:
- Go slices and strings basics
- Understanding of unsafe and memory layout
- go
- string
- unsafe
- zero-copy
- string-header
- memory
Read at your depth
The practical view
A Go string is an immutable header: a pointer to the first byte and a length. Reading a string is cheap (no copy). Converting []byte to string normally COPIES (to preserve immutability): string(b). Similarly string to []byte copies. For performance-critical paths you can do zero-copy conversions with unsafe: unsafe.String(ptr, len) builds a string from bytes without copying, and unsafe.StringData(s) gets the byte pointer. unsafe.SliceData and unsafe.Slice convert between slices and their backing arrays. These are DANGEROUS — mutating the result violates immutability — use only when the lifetime and read-only usage are guaranteed.
The same idea in other frameworks
N/A — Go runtime concern.N/A — Go concern.N/A — Go concern.N/A — Go concern.Legacy vs modern
Copying byte→string conversion vs zero-copy unsafe
string(b) copies the bytes; unsafe.String(b) reuses the same memory — a big win in hot parsers, with a safety contract.
key := string(buf) // ❌ OLD: copies every time — allocation in a looptokStr := unsafe.String(&tok[0], len(tok)) // ✅ NEW: no copy, shared memoryInterview gotchas
Context
This tests the immutability contract — interviewers want to know the memory-safety reason behind the copy and the specific corruption scenarios that zero-copy would enable.
The mechanical answer
A string in Go is immutable BY CONTRACT: the runtime and libraries assume its bytes never change (map keys are hashed and stored, strings are used as keys in hash tables and compared cheaply, the compiler may intern or share them). A []byte is mutable by design. If string(b) didn't copy but shared the slice's backing array, then mutating the slice afterward would silently change the string — breaking map lookups (a key's hash changes after insertion), corrupting shared string data, and violating the language guarantee. So the conversion copies to give the string its own, immutable storage. Zero-copy via unsafe.String skips this protection and is only sound if the source memory is guaranteed immutable and correctly lifetime-managed — which is why the unsafe path exists for performance-critical code with a strict contract.
Trap
The naive answer is 'the copy is just for safety against mutation of the string'. It's specifically because the SOURCE slice could be mutated; the string itself is never writable. Another trap: thinking unsafe.String is safe to use freely — a string built over a slice that later grows/reallocates (append) dangles, because the backing array moved. Candidates who describe the immutability-dependent runtime guarantees show real understanding.
Context
This probes the memory-header model — interviewers want the 2-word vs 3-word headers and the exact unsafe accessors, which underpin zero-copy design.
The mechanical answer
A string is a 2-word header: { data unsafe.Pointer, len int } (no capacity — strings have no cap). A slice is a 3-word header: { data unsafe.Pointer, len int, cap int }. unsafe.StringData(s) returns the data pointer of a string (the first byte's address); you can then build a byte slice over it with unsafe.Slice(ptr, len) to read the bytes without copying. unsafe.SliceData(slice) returns the backing array's first-element pointer; unsafe.Slice(ptr, len) builds a new slice header over arbitrary memory. These accessors let you decompose and reassemble headers for zero-copy transforms (e.g. treating a string's bytes as a byte slice for hashing, or slicing into a raw allocation). All are governed by the unsafe rules: alignment, lifetime, and immutability are your responsibility.
Trap
The naive answer is 'strings and slices are the same header'. Strings have no cap and are immutable; slices carry cap and are mutable. Another trap: calling unsafe.StringData on a zero-length string returns a pointer that may be nil or to a runtime sentinel — don't dereference it. Candidates who describe the exact header layouts and the accessor contracts show real depth.