Saltar al contenido

Iteradores Personalizados

En esta página

El for x in expr de Zolo se impulsa mediante dos traits de core::iter:

  • Iterator<T> — un cursor con estado; next(self) -> T? lo avanza. Devolver nil señala agotamiento.
  • IntoIterator<T> — un contenedor que puede producir un cursor fresco via iter(self). for x in container llama a iter() automáticamente.

Cualquier tipo que implemente alguno de estos traits se conecta a for sin código adicional.

Cursor personalizado con for, un contenedor IntoIterator, enumeración for i, x, y cadena de adaptadores.

15-custom-iterators.zolo
Playground
// Feature: Custom iterators

// Traits: `Iterator<T>` (cursor) and `IntoIterator<T>` (producer)

// Works on: VM, native, wasm-aot

// Note: adapter chains (.map/.filter/.sum/…) work on VM only.


// `Iter` (the adapter type returned by `iter()`) lives in std.

use std::Iter

// ── 1. Iterator<T> — direct cursor ──────────────────────────────────────────

//

// Implement `Iterator<T>` to make a struct driveable by `for x in`.

// The single required method is `next(self) -> T?`; returning `nil` signals

// the end of the sequence.


struct Countdown { n: int }

impl Iterator<int> for Countdown {
    fn next(self) -> int? {
        if self.n <= 0 { return nil }
        self.n = self.n - 1
        return self.n + 1
    }
}

// ── 2. IntoIterator<T> — container that produces a cursor ───────────────────

//

// Implement `IntoIterator<T>` when the iterable thing is a *container*.

// `iter()` returns a fresh cursor; `for x in container` calls `iter()` first.


struct Bag { items: [int] }

impl IntoIterator<int> for Bag {
    fn iter(self) -> Iter<int> { return Iter.from(self.items) }
}

fn main() {
    // for loop over a direct Iterator

    let c = Countdown { n: 3 }
    for x in c { print(x) }
    // expected: 3

    // expected: 2

    // expected: 1


    // Manual sum via for loop (works on all backends)

    var total = 0
    let c2 = Countdown { n: 3 }
    for x in c2 { total = total + x }
    print(total)
    // expected: 6


    // IntoIterator container

    let b = Bag { items: [10, 20, 30] }
    for x in b { print(x) }
    // expected: 10

    // expected: 20

    // expected: 30


    // for i, x enumerate (0-based index)

    let arr = [100, 200, 300]
    for i, x in arr { print(i) print(x) }
    // expected: 0

    // expected: 100

    // expected: 1

    // expected: 200

    // expected: 2

    // expected: 300


    // Adapter chain on custom iterator — VM only

    // (on native/wasm-aot use the for-loop form above)

    let c3 = Countdown { n: 5 }
    let mapped_sum = c3.map(|x| x * 2).filter(|x| x > 4).sum()
    print(mapped_sum)
    // expected: 24

}

Iterator<T> — cursor directo

struct Countdown { n: int }

impl Iterator<int> for Countdown {
    fn next(self) -> int? {
        if self.n <= 0 { return nil }
        self.n = self.n - 1
        return self.n + 1
    }
}

fn main() {
    let c = Countdown { n: 3 }
    for x in c { print(x) }   // 3, 2, 1
}

IntoIterator<T> — contenedor

struct Bag { items: [int] }

impl IntoIterator<int> for Bag {
    fn iter(self) -> Iter<int> { return Iter.from(self.items) }
}

fn main() {
    let b = Bag { items: [1, 2, 3] }
    for x in b { print(x) }   // 1, 2, 3
}

for x in b se expande llamando a b.iter() y conduciendo el cursor devuelto.

for i, x — enumeración (base 0)

let arr = [10, 20, 30]
for i, x in arr { print(i) print(x) }
// 0  10  1  20  2  30

Funciona con arrays, contenedores IntoIterator y literales de mapa (donde i es la clave y x el valor).

Cadenas de adaptadores — solo VM

En la VM, un iterador de usuario puede envolverse en el adaptador de la prelude Iter<T>, dando acceso a la suite completa map/filter/take/fold/sum/collect/…:

let result = Countdown { n: 5 }
    .map(|x| x * 2)
    .filter(|x| x > 4)
    .sum()
// 24

Limitación de backend: las cadenas de adaptadores (.map().filter().sum()) y la desestructuración for k, v in mapLiteral funcionan solo en la VM. En native (Cranelift) y wasm-aot estos backends carecen del runtime del adaptador Lua Iter.*. Usa la forma de bucle for cuando necesites portabilidad entre backends.

Bounds genéricos

Una función puede aceptar cualquier Iterator<T> mediante un bound genérico:

fn sum_all<I: Iterator<int>>(it: I) -> int {
    var total = 0
    for x in it { total = total + x }
    return total
}

Iterator es un trait conocido; el bound es aceptado por el compilador.

Limitación en tiempo de ejecución — usa for dentro de cuerpos genéricos. Llamar a métodos adaptadores (.fold/.map/…) directamente sobre un valor genérico I: Iterator falla en tiempo de ejecución en todos los backends. El parámetro genérico it es una struct de usuario, no un envoltorio Iter, por lo que rawget no encuentra ningún slot de adaptador en él. Usa siempre un bucle for x in it { … } dentro del cuerpo de una función genérica.

Nota: la verificación del tipo de elemento en los sitios de llamada (verificar que I es específicamente Iterator<int> y no Iterator<str>) es una mejora conocida para el futuro.

El centinela nil

next() devuelve T? — nullable. El centinela de fin de secuencia es nil. Esto significa que una secuencia cuyos elementos legítimos pueden ser nil no puede iterarse con este protocolo. Diseña tales secuencias con un tipo envolvente.

Errores

Código Significado
TE828 for x in expr cuando el tipo de expr no implementa Iterator ni IntoIterator.

Consulta también

Buscar en Zolo

9 resultados

enespt-br