En Go, la conversión entre string y []byte generalmente implica la asignación de nueva memoria, ya que las cadenas son inmutables y los slices de bytes son mutables. Las excepciones son algunas optimizaciones internas del compilador (escape analysis), pero no son garantizadas y cambian entre versiones de Go.
Conversión directa:
[]byte(s string): siempre asigna un nuevo array de bytes y copia los datos.string(b []byte): también siempre copia los datos del array de bytes a una nueva cadena.Zero allocation — un enfoque en el que evitamos asignaciones innecesarias. En la biblioteca estándar de Go hay una conversión insegura a través del paquete unsafe, que permite referenciar los mismos datos sin copia. Esto solo se debe utilizar con un pleno entendimiento de los riesgos.
import ( "reflect" "unsafe" ) func BytesToString(b []byte) string { return *(*string)(unsafe.Pointer(&b)) } func StringToBytes(s string) []byte { sh := (*reflect.StringHeader)(unsafe.Pointer(&s)) bh := reflect.SliceHeader{ Data: sh.Data, Len: sh.Len, Cap: sh.Len, } return *(*[]byte)(unsafe.Pointer(&bh)) }
En general, salvo en casos especiales, es mejor no utilizar estas técnicas, ya que rompen la seguridad de los datos (se puede modificar el contenido de la cadena a través de un slice compartido, lo que puede llevar a errores).
Se pregunta a menudo: "¿Es cierto que al convertir string a []byte no habrá asignación si la cadena es pequeña o constante?"
Respuesta correcta: No. Independientemente del tamaño de la cadena, el compilador siempre creará un nuevo slice de bytes y copiará el contenido. Las excepciones son solo en optimizaciones inseguras, que no garantizan seguridad y no están respaldadas por la documentación oficial de Go.
Historia
El equipo estaba escribiendo un servicio de alta carga para analizar logs y constantemente convertía las cadenas entrantes a slices de bytes y viceversa para su procesamiento. En picos de carga, el recolector de basura consumía hasta un 30% del tiempo de CPU en la recolección de copias de corta vida. Después de un perfilado, se descubrió que cada conversión string↔[]byte asignaba un área de memoria separada. Después de implementar pools y rediseñar la API, se pudo eliminar una parte sustancial de las conversiones, reduciendo la carga en el GC a la mitad.
Historia
Uno de los desarrolladores optimizó el trabajo con JSON, utilizando la conversión insegura bytes→string para evitar asignaciones. Al principio, el aumento de rendimiento fue notable, pero tras un mes aparecieron fallos: algún buffer de bytes se reutilizaba, y la cadena apuntaba a datos antiguos y modificados. Solo se pudo corregir volviendo a las copias estándar y rehaciendo la API interprocesal.
Historia
Al transmitir grandes datos binarios a través de la red, decidieron "optimizar" la serialización usando BytesToString (sin copia). Un día, la cadena que enviaron resultó ser públicamente visible y el contenido del slice de bytes fue sobrescrito, lo que llevó a enviar basura y filtrar partes privadas de datos en el log de paquetes erróneos. Al final, la deduplicación de memoria resultó en la fuga de datos privados!