Trabajar con texto en Go implica entender tres tipos fundamentales: string, []byte y []rune. Un string es una secuencia inmutable de bytes que representa texto; esto significa que una vez creado, su contenido no puede ser alterado. Si necesitas modificar el contenido, debes convertirlo a un []byte, que es una rebanada (slice) de bytes mutables. Por otro lado, cuando trabajas con caracteres especiales como emojis o tildes, un []byte puede quedarse corto porque esos caracteres ocupan más de un byte en memoria (UTF-8). Ahí es donde entra el []rune, que trata el texto como una secuencia de code points (caracteres individuales de Unicode), sin importar cuántos bytes ocupen.
Esta distinción existe por una razón de diseño: la inmutabilidad. Si convertir un string a []byte no creara una copia nueva de los datos, podrías modificar el slice de bytes y, por consecuencia, cambiar el valor del string original, rompiendo la garantía de seguridad del lenguaje. Por eso, cada vez que realizas estas conversiones, Go realiza una copia de los datos en la memoria. Esta distinción es vital: usar estas conversiones es necesario para manipular datos, pero hacerlo de forma excesiva o dentro de bucles muy intensivos puede penalizar el rendimiento de tu aplicación debido a la presión sobre el Garbage Collector (GC). Si intentas tratar un string como si fuera un array de caracteres usando solo len() o índices, podrías terminar rompiendo un carácter a la mitad si este contiene múltiples bytes.
package main
import (
"fmt"
)
func main() {
// Un string es inmutable y su longitud es en bytes.
original := "Hola, 🚀"
fmt.Printf("Original: %s (bytes: %d)\n", original, len(original))
// 1. De string a []byte: Creamos una copia para poder editar.
// Si modificamos 'bytes', 'original' permanece intacto.
bytes := []byte(original)
bytes[5] = '!' // Cambiamos la coma por un signo de exclamación
fmt.Printf("Modificado con bytes: %s\n", string(bytes))
// 2. De string a []rune: Decodificamos UTF-8 para ver caracteres reales.
// El emoji '🚀' ocupa 4 bytes, pero es 1 solo rune.
runes := []rune(original)
fmt.Printf("Cantidad de runes: %d\n", len(runes)) // Cuenta caracteres, no bytes
fmt.Printf("El segundo carácter es: %c\n", runes[1])
// 3. De rune a string: Convertimos un solo code point a texto.
r := runes[5] // Obtenemos el emoji '🚀'
fmt.Printf("Rune a string: %s\n", string(r))
// Demostración de que string(r) es una forma válida de reconstruir texto
fmt.Printf("Texto reconstruido: %s%s\n", "Emoji: ", string(r))
}
Desglose del ejemplo
Fíjate en la variable original. Aunque parece simple, len(original) nos devuelve 10 porque el emoji 🚀 consume 4 bytes en la codificación UTF-8.
Cuando hacemos bytes := []byte(original), Go reserva un nuevo bloque de memoria y copia esos 10 bytes allí. Por eso, cuando modificamos bytes[5], el cambio solo afecta a nuestra copia; el string original no se ve afectado, manteniendo su inmutabilidad.
La conversión runes := []rune(original) es la más costosa de las tres. El runtime tiene que recorrer todos los bytes, decodificar la secuencia UTF-8 y transformarla en números de 32 bits (tipo int32 que es lo que representa un rune). Esto es lo que nos permite que len(runes) nos devuelva 7 en lugar de 10, ya que nos está contando caracteres reales, no piezas de memoria.
Finalmente, cuando hacemos string(r), donde r es un rune, Go toma ese número entero y lo convierte nuevamente en su representación de texto UTF-8. Es la forma más limpia de convertir un carácter aislado en una cadena de texto para concatenarla o imprimirla.
El error frecuente ocurre cuando intentas manipular un string como si fuera un array de caracteres usando índices. Por ejemplo, si intentas acceder al “tercer carácter” de una cadena que contiene emojis haciendo original[3], no obtendrás el tercer carácter, sino el cuarto byte de la secuencia. Esto puede resultar en un valor inválido o en un carácter mal formado. Si tu lógica depende de la posición de los caracteres y no de la posición de los bytes, usa siempre []rune.
N° 31