Pular para o conteúdo

Iteradores Personalizados

Nesta página

O for x in expr do Zolo é dirigido por dois traits de core::iter:

  • Iterator<T> — um cursor com estado; next(self) -> T? o avança. Retornar nil sinaliza esgotamento.
  • IntoIterator<T> — um container que pode produzir um cursor fresco via iter(self). for x in container chama iter() automaticamente.

Qualquer tipo que implemente algum desses traits se conecta ao for sem código adicional.

Cursor personalizado com for, um container IntoIterator, enumeração for i, x, e cadeia 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 direto

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> — container

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 chamando b.iter() e conduzindo o cursor retornado.

for i, x — enumeração (base 0)

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

Funciona com arrays, containers IntoIterator e literais de mapa (onde i é a chave e x o valor).

Cadeias de adaptadores — somente VM

Na VM, um iterador de usuário pode ser envolto no adaptador da prelude Iter<T>, dando acesso à suite completa map/filter/take/fold/sum/collect/…:

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

Limitação de backend: cadeias de adaptadores (.map().filter().sum()) e a desestruturação for k, v in mapLiteral funcionam somente na VM. No native (Cranelift) e wasm-aot esses backends não possuem o runtime do adaptador Lua Iter.*. Use a forma de laço for quando precisar de portabilidade entre backends.

Bounds genéricos

Uma função pode aceitar qualquer Iterator<T> via um 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 é um trait conhecido; o bound é aceito pelo compilador.

Limitação em tempo de execução — use for dentro de corpos genéricos. Chamar métodos adaptadores (.fold/.map/…) diretamente sobre um valor genérico I: Iterator falha em tempo de execução em todos os backends. O parâmetro genérico it é uma struct de usuário, não um wrapper Iter, portanto rawget não encontra nenhum slot de adaptador nele. Sempre use um laço for x in it { … } dentro do corpo de uma função genérica.

Nota: a verificação do tipo de elemento nos sites de chamada (verificar que I é especificamente Iterator<int> e não Iterator<str>) é uma melhoria conhecida para o futuro.

O sentinela nil

next() retorna T? — anulável. O sentinela de fim de sequência é nil. Isso significa que uma sequência cujos elementos legítimos podem ser nil não pode ser iterada com este protocolo. Projete tais sequências com um tipo invólucro.

Erros

Código Significado
TE828 for x in expr quando o tipo de expr não implementa Iterator nem IntoIterator.

Veja também

Buscar no Zolo

9 resultados

enespt-br