Un string de Go es una secuencia de bytes, y el código fuente y los literales de string de Go están en UTF-8. Un byte es uno de esos bytes. Un rune es un carácter Unicode (un code point), que UTF-8 guarda en 1 a 4 bytes. Casi toda la confusión con los strings en Go viene de mezclar los dos:
語 ocupa tres bytes en UTF-8, así que "語Go" son 5 bytes pero 3 runes. El bucle con índice ve cinco bytes, y los bytes 0, 1 y 2 no significan nada por separado. El bucle range decodifica UTF-8 y produce tres runes en los índices 0, 3 y 4: i es siempre un desplazamiento en bytes, no un recuento de caracteres.
byte y rune son alias
| Nombre | Mismo tipo que | Literal | Significado |
|---|---|---|---|
byte | uint8 | byte('A'), 0x41 | un byte de datos |
rune | int32 | 'A', 'é', '世' | un code point Unicode |
Como son alias, un byte es un uint8 y un rune es un int32; no hace falta conversión entre el alias y su tipo subyacente. Las comillas simples crean un rune y las dobles un string. 'ab' es un error de compilación, porque un literal rune contiene exactamente un carácter.
Los runes son números, así que puedes hacer aritmética con ellos:
'7' - '0' es la forma clásica de convertir un carácter de dígito en su valor. El paquete unicode clasifica runes (IsLetter, IsDigit, IsSpace, IsUpper) y cambia mayúsculas y minúsculas correctamente en letras que no son ASCII. %c imprime un rune como carácter, %U en la forma U+4E16 y %q como literal rune entre comillas.
Cómo guarda UTF-8 los caracteres
| Code points | Bytes | Ejemplos |
|---|---|---|
| U+0000 a U+007F | 1 | ASCII: a, 7, { |
| U+0080 a U+07FF | 2 | é, ß, ж, ع |
| U+0800 a U+FFFF | 3 | 語, €, 한 |
| U+10000 y superiores | 4 | emoji, CJK poco común |
El texto ASCII es idéntico en UTF-8, y por eso el código basado en bytes suele funcionar en las pruebas y romperse con el primer nombre con tilde. El paquete unicode/utf8 trabaja directamente con la codificación:
% x (con un espacio) imprime los bytes en hexadecimal separados por espacios. utf8.ValidString informa de si un string es UTF-8 válido. Los strings de Go pueden contener cualquier byte, no solo UTF-8 válido; cuando range encuentra un byte no válido, produce utf8.RuneError (U+FFFD, el carácter de sustitución) y avanza un byte.
Convertir entre string, []byte y []rune
| Conversión | Resultado | Coste |
|---|---|---|
[]byte(s) | los bytes UTF-8 | copia |
[]rune(s) | code points decodificados | decodifica y copia (4 bytes por rune) |
string(b) | un string a partir de bytes | copia |
string(r) donde r es []rune | codifica a UTF-8 | copia |
string(r) donde r es un rune | un string de un carácter | pequeño |
Invertir por bytes convertiría 世界 en UTF-8 no válido, y por eso reverse trabaja con runes. Ni siquiera los runes lo cuentan todo: un carácter como é también se puede escribir como e más un acento combinable, que son dos runes. Para los caracteres que ve el usuario (grapheme clusters), usa una librería como github.com/rivo/uniseg.
Cada conversión copia, porque los strings son inmutables y los slices no. En código caliente, evita convertir de ida y vuelta; el compilador optimiza algunos casos (como string(b) usado como clave de map o en una comparación) para saltarse la copia.
Indexar da un byte
s[i] devuelve un byte, y s[i:j] recorta por desplazamientos en bytes:
s := "café"
fmt.Println(s[3]) // 195: the first byte of é
fmt.Println(string(s[3])) // "Ã": that byte read as a code point
fmt.Println(s[:3]) // "caf"
fmt.Println(s[:4]) // "caf\xc3": half of é, invalid UTF-8
string(s[3]) es una trampa: convertir un solo byte a string lo trata como el code point 195, que es Ã, no como el propio byte. Para encontrar los límites de los caracteres, usa range, la familia strings.Index (que devuelve desplazamientos en bytes que siempre caen en un límite) o utf8.DecodeRuneInString.
El paquete bytes
bytes replica strings para []byte: bytes.Contains, bytes.Split, bytes.Fields, bytes.TrimSpace, bytes.Equal, bytes.ToUpper, etc. Úsalo cuando los datos llegan como bytes (contenido de archivos, bodies HTTP, buffers de red), para no convertirlos a string y de vuelta:
bytes.Buffer es un buffer de bytes que crece y que implementa io.Reader e io.Writer. Es la herramienta adecuada cuando escribes y lees bytes; para construir un string, strings.Builder es algo más barato. Compara slices de bytes con bytes.Equal, porque == no funciona con slices.
Cuál elegir
- Texto que muestras, comparas o buscas: mantenlo como
string, y usarangecuando lo recorras carácter a carácter. - Posiciones de caracteres, invertir, truncar a n caracteres: convierte a
[]rune. - I/O, hashing, codificación, protocolos binarios: usa
[]bytey el paquetebytes. - Un solo carácter: un
rune. Un solo byte en bruto: unbyte.
Preguntas frecuentes
¿Qué es un rune en Go?
rune es un alias de int32 y representa un code point Unicode. Un literal rune se escribe entre comillas simples: 'a' es 97, 'é' es 233, '世' es 19990. Recorrer un string con for i, r := range s te da runes, decodificados a partir de los bytes UTF-8 del string.
¿Qué diferencia hay entre un rune y un byte en Go?
Un byte (alias de uint8) son 8 bits de datos en bruto. Un rune (alias de int32) es un carácter Unicode completo. En un string UTF-8, los caracteres ASCII ocupan un byte cada uno, mientras que los demás ocupan de dos a cuatro bytes pero siguen siendo un solo rune. len(s) cuenta bytes; utf8.RuneCountInString(s) cuenta runes.
¿Cómo recorro los caracteres de un string en Go?
Usa for i, r := range s. Cada iteración da el desplazamiento en bytes i y el rune r, así que los caracteres de varios bytes se tratan correctamente. Un bucle con índice normal for i := 0; i < len(s); i++ visita bytes, lo que parte en trozos los caracteres que no son ASCII.
¿Cómo convierto un string en un slice de bytes en Go?
b := []byte(s) copia los bytes del string en un slice nuevo. string(b) hace la conversión inversa, también con copia. Usa []rune(s) cuando necesites trabajar con caracteres, por ejemplo para invertir un string o tomar sus primeros n caracteres.