vlang / v

Simple, fast, safe, compiled language for developing maintainable software. Compiles itself in <1s with zero library dependencies. Supports automatic C => V translation. https://vlang.io
MIT License
35.75k stars 2.16k forks source link

Iterator next() is cloning the object and not allowing access to the iter object #12411

Open jdonnerstag opened 2 years ago

jdonnerstag commented 2 years ago

V version: V 0.2.4 52df19e OS: windows, Microsoft Windows 10 Enterprise v19042 64-bit

What did you do?

struct MyIter {
mut:
    ar []int
    pos int
}

fn (mut m MyIter) next() ? int {
    for m.pos < m.ar.len {
        x := m.ar[m.pos]
        m.pos ++
        if x >= 5 { return x }
    }
    return error('')
}

type Intar = []int

fn (i Intar) my_filter() MyIter {
    return MyIter{ ar: i, pos: 0 }
}

fn test_iters() ? {
    data := Intar([1, 2, 5, 9, 3, 6, 0])
    //for x in data.filter() {  // conflicts with built-in filter() function
    for x in data.my_filter() {
        eprintln("x: $x")
    }

    mut iter := data.my_filter()
    for x in iter {
        eprintln("x: $x => $iter.pos")   // (1)
    }
}

What did you expect to see? I expected to see that at (1) the updated pos value gets printed. Instead it is always the initial value. Which means that even in the 2nd test case (for x in iter {), V clones the object. And that means, I don't have access to it. And I think there is no way, to get access to the iter object that is being used by next().

What did you see instead? At (1) always the initial value of pos is printed, but never the updated / real one.

jdonnerstag commented 2 years ago

The C-snippet confirms my understanding

    rosie__parser__rpl__Intar data = ((new_array_from_c_array(7, 7, sizeof(int), _MOV((int[7]){1, 2, 5, 9, 3, 6, 0}))));
    rosie__parser__rpl__MyIter _t1 = rosie__parser__rpl__Intar_my_filter(data);
    while (1) {
        Option_int _t2 = rosie__parser__rpl__MyIter_next(&_t1);
        if (_t2.state != 0) break;
        int x = *(int*)_t2.data;
        eprintln( str_intp(2, _MOV((StrIntpData[]){{_SLIT("x: "), 0xfe07, {.d_i32 = x}}, {_SLIT0, 0, { .d_c = 0 }}})));
    }
    rosie__parser__rpl__MyIter iter = rosie__parser__rpl__Intar_my_filter(data);
    rosie__parser__rpl__MyIter _t3 = iter;
    while (1) {
        Option_int _t4 = rosie__parser__rpl__MyIter_next(&_t3);

_t1 and _t3 are (internal) clones not access from V. At least in the 2nd example, _t3 is superfluous and not needed. V needs to detect that a variables has been declared already and leverage that, rather then making another copy.

jdonnerstag commented 2 years ago

A workaround is not complicated, but it took me half a day to realise what is actually going wrong. Of course first I assumed I'm doing something wrong, until I figured it out. If properly supportting it is too complicated or cumbersome at first, then at least a useful warning or error message would be good.

mut iter := data.my_filter()
for {
    x := iter.next() or { break }