How do you do zero-copy []byte ↔ string conversion correctly, and what can go wrong?
Question 353HardGo 1.22 to 1.25
Since Go 1.20, use the dedicated functions and not the old header casts:
// BytesToString: b must not be modified while the string is in use.
func BytesToString(b []byte) string {
return unsafe.String(unsafe.SliceData(b), len(b))
}
// StringToBytes: the result must NEVER be written to.
func StringToBytes(s string) []byte {
return unsafe.Slice(unsafe.StringData(s), len(s))
}
What goes wrong:
- Mutating a string's bytes: string literals live in read-only memory, so writing to
StringToBytes("hi")[0]is a segfault thatrecovercannot catch. For heap-allocated strings, mutation silently breaks immutability: map keys become corrupted and interned strings change behind other code. - Reusing the byte buffer: if
bcomes from a pool orbufio.Scanner.Bytes(), the "string" changes when the buffer is refilled. Storing it as a map key corrupts the map. - Empty input:
unsafe.StringData("")may be nil, which is still fine to pass tounsafe.Slicewith length 0. appendon the result ofStringToBytesis safe only because cap equals len, so it must reallocate. Never extend it by reslicing.
What the interviewer is looking for: "measure first". The compiler already removes many copies, as described above. Use unsafe conversion only in a proven hot path with strict ownership rules, keep it in one small audited helper, and document the invariant.
More on Memory, GC & Runtime Internals
- Q351What are the rules for valid
unsafe.Pointerusage? - Q352Why is keeping a pointer as
uintptrdangerous even if the object is still referenced elsewhere? - Q354What does this program print? Explain how finalizers behave.
- Q355What is
runtime.AddCleanupand why is it preferred overSetFinalizer? - Q356What are weak pointers in Go and when would you use them?
- Q357What does
runtime.KeepAlivedo and when is it required?