La iteración range sobre una cadena de texto en Go realiza una decodificación automática de la secuencia de bytes subyacente en puntos de código Unicode, conocidos técnicamente como runas. A diferencia de un bucle for tradicional basado en contadores que accede a índices de bytes individuales, el operador range interpreta el contenido siguiendo la codificación UTF-8, devolviendo en cada iteración la posición inicial del byte y el valor de la runa correspondiente (tipo rune, que es un alias de int32).
Este comportamiento está integrado en el núcleo del lenguaje para mitigar errores de segmentación de datos al manipular texto internacional. En lenguajes donde los strings se tratan exclusivamente como arrays de caracteres de ancho fijo o simples buffers de bytes, es frecuente que los desarrolladores corrompan accidentalmente caracteres multibyte al realizar operaciones de corte (slicing) o iteraciones ingenuas. Go resuelve este problema forzando que la unidad mínima de iteración semántica sea la runa, garantizando la integridad de los caracteres independientemente de cuántos bytes ocupen en memoria.
Internamente, una variable de tipo string es una estructura de cabecera de solo lectura que apunta a un array de bytes. Cuando se ejecuta for index, runeValue := range s, el runtime invoca una lógica de decodificación que consume uno o más bytes para reconstruir el punto de código Unicode. Es imperativo comprender que el primer valor retornado es el índice del byte donde comienza la runa actual dentro del underlying byte slice. Si el carácter en la posición i requiere tres bytes, la siguiente iteración no devolverá i+1, sino i+3. Esta discrepancia técnica es fundamental al calcular longitudes o realizar desplazamientos de punteros, ya que len(s) devuelve el conteo de bytes y no el conteo de runas.
package main
import "fmt"
func main() {
// "Gó" contiene 'ó', que ocupa 2 bytes en UTF-8
s := "Gó!"
fmt.Printf("Longitud en bytes: %d\n", len(s)) // → 4
for i, r := range s {
// %q muestra el caracter literal, %d el punto de código
fmt.Printf("Índice: %d | Runa: %q | Punto de código: %d\n", i, r, r)
}
}
/* Output:
Longitud en bytes: 4
Índice: 0 | Runa: 'G' | Punto de código: 71
Índice: 1 | Runa: 'ó' | Punto de código: 243
Índice: 3 | Runa: '!' | Punto de código: 33
*/
GoEl comportamiento más contraintuitivo del ejemplo es el salto del índice 1 al índice 3. Dado que la runa ‘ó’ (U+00F3) está codificada en UTF-8 como la secuencia de bytes 0xC3 0xB3, el runtime consume ambos bytes para entregar una sola runa, pero el contador de índice refleja fielmente la dirección física del inicio de cada secuencia en el array subyacente.
Gestión de secuencias mal formadas y el reemplazo U+FFFD
Un aspecto crítico del mecanismo de iteración es cómo el runtime de Go gestiona los datos que no cumplen con la especificación UTF-8. Si una cadena contiene una secuencia de bytes inválida, range no detiene la ejecución ni lanza un error en tiempo de ejecución. En su lugar, consume un único byte erróneo y devuelve la runa de reemplazo Unicode \uFFFD (representada visualmente como ).
Este comportamiento garantiza que la iteración sea siempre segura y predecible, evitando bloqueos por datos corruptos. Sin embargo, para un ingeniero de sistemas, esto implica que el éxito de una iteración range no es prueba suficiente de que la cadena sea una secuencia UTF-8 válida. Si la integridad de los datos es un requisito estricto, se debe emplear el paquete unicode/utf8 para validar la cadena antes de procesarla. Un edge case real ocurre al recibir flujos de datos binarios truncados; el desarrollador podría procesar múltiples runas de reemplazo sin notar que la codificación original se ha perdido, a menos que verifique explícitamente si el valor de la runa coincide con utf8.RuneError.
- Módulo: Control de Flujo
- Artículo número: #58