Iteradores for-range sobre funciones en Go 1.23

La iteración sobre funciones en Go 1.23, conocida técnicamente como range-over-functions, permite que la instrucción for-range consuma una función iteradora con una firma específica para controlar el flujo de un bucle. Esta capacidad transforma el comportamiento del lenguaje al permitir que tipos de datos complejos y estructuras de datos personalizadas expongan una interfaz de recorrido uniforme sin revelar su implementación interna.

Antes de esta actualización, Go carecía de una convención estricta para iteradores; los desarrolladores dependían de cierres (closures) personalizados, canales o métodos que devolvían punteros, lo cual generaba inconsistencias y sobrecarga en el runtime. La introducción de iteradores en la versión 1.23 unifica este patrón bajo el paquete iter, proporcionando firmas de funciones que el compilador reconoce para desglosar el bucle for en llamadas sucesivas a un callback denominado yield.

El mecanismo se fundamenta en dos firmas principales: func(yield func() bool) para iteraciones sin valores, y las más comunes iter.Seq[V] e iter.Seq2[K, V]. En términos de assignability, cualquier función que cumpla con la firma func(yield func(T) bool) bool puede ser el objetivo de una cláusula range. El compilador transforma el cuerpo del bucle en la función yield; si el bucle encuentra un break, la función yield devuelve false, señalando al iterador que debe detener la generación de valores y ejecutar cualquier lógica de limpieza pendiente.

package main

import (
	"fmt"
	"iter"
)

// List representa una colección simple de enteros
type List struct {
	elements []int
}

// All devuelve un iterador iter.Seq2 (índice, valor)
func (l *List) All() iter.Seq2[int, int] {
	return func(yield func(int, int) bool) {
		for i, v := range l.elements {
			// Si el consumidor del iterador hace break, yield retorna false
			if !yield(i, v) {
				return
			}
		}
	}
}

func main() {
	myList := List{elements: []int{10, 20, 30, 40}}

	// Uso nativo de for-range sobre la función iteradora
	for i, val := range myList.All() {
		if val > 25 {
			break
		}
		fmt.Printf("Idx: %d, Val: %d\n", i, val)
	}
	// Output:
	// Idx: 0, Val: 10
	// Idx: 1, Val: 20
}
Go

La ejecución de este patrón implica que el estado de la iteración se mantiene dentro del scope de la función iteradora, lo que facilita el recorrido de estructuras no lineales, como árboles o grafos, manteniendo la seguridad de tipos y evitando la asignación innecesaria de memoria que requeriría volcar los datos en un slice intermedio.

Propagación de pánicos y ejecución de defer en el stack del iterador

Un comportamiento crítico del runtime en los iteradores de Go 1.23 es la gestión del stack cuando ocurre un pánico dentro del cuerpo del bucle. Dado que el iterador y el bucle comparten el mismo stack de ejecución de forma lógica pero operan mediante llamadas de retorno, un panic iniciado dentro del bucle for-range se propagará a través de la función iteradora.

Esto garantiza que las sentencias defer declaradas dentro de la función iteradora se ejecuten correctamente antes de que el pánico suba por la cadena de llamadas. Si el iterador estuviera gestionando un recurso externo, como un descriptor de archivo o un mutex, el flujo garantiza la liberación del recurso incluso si el código del consumidor falla catastróficamente. Sin embargo, es vital recordar que un iterador no debe intentar atrapar un pánico del consumidor mediante recover para silenciarlo, ya que esto rompería la semántica de control de errores esperada por el programador del bucle principal.

Módulo: Iteradores

Artículo número: #62

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio