Skip to content

Have for pattern in iter not borrow the iterator #8372

Closed

Description

@SimonSapin

Test case:

let mut iter = range(1, 10);
for i in iter {
    printfln!(i);
    // error: cannot borrow `iter` as mutable more than once at a time
    printfln!(iter.next());
}

The above currently fails, but I think it should be valid and equivalent to:

let mut iter = range(1, 10);
loop { match iter.next() { None => break, Some(i) => {
    printfln!(i);
    printfln!(iter.next());
}}

In other words, the for loop should only borrow the iterator long enough to call next(), leaving the loop body free to use it.

I understand that this might not be possible when a more complex expression is used as the iterator. The for loop needs to evaluate the expression once and keep the result in an anonymous variable, thus borrowing. Maybe there could be a special case when the iterator expression is "simple" enough: a variable, struct field access, etc?

Activity

  1. thestinger commented on Aug 7, 2013

    @thestinger
    Contributor

    This means implementing it as more than a simple parser feature. It's not possible to check the type of the passed in variable at the moment and decide whether a temporary is needed.

  2. huonw commented on Aug 7, 2013

    @huonw
    Contributor

    @thestinger for is likely to need to be a non-parser feature anyway: it is currently duck-typed, and gives horrible error messages. (As I'm sure you know.)

  3. SimonSapin commented on Aug 7, 2013

    @SimonSapin
    ContributorAuthor

    @thestinger what you say might still be true, but this is the type of AST node (eg. identifier) for the iterator, not Rust type (eg. std::vec::VecIterator.)

  4. SimonSapin commented on Aug 7, 2013

    @SimonSapin
    ContributorAuthor

    Also, this is a "nice to have", but not blocking because I can just use loop with match. I even have a macro for it:

    // Work around "error: cannot borrow `iter` as mutable more than once at a time"
    // when using a normal for loop.
    macro_rules! for_iter(
        ($iter: expr, $pattern: pat, $loop_body: expr) => (
            loop {
                match $iter.next() { None => break, Some($pattern) => $loop_body }
            }
        );
    )
    
  5. bluss commented on Aug 7, 2013

    @bluss
    Contributor

    Your macro should probably take an ident, not an expression since it doesn't work well with expressions!

  6. SimonSapin commented on Aug 7, 2013

    @SimonSapin
    ContributorAuthor

    Good point, @blake2-ppc.

    // Work around "error: cannot borrow `iter` as mutable more than once at a time"
    // when using a normal for loop.
    macro_rules! for_iter(
        ($iter: ident, $pattern: pat, $loop_body: expr) => (
            loop {
                match $iter.next() { None => break, Some($pattern) => $loop_body }
            }
        );
    )
    
  7. huonw commented on Nov 15, 2013

    @huonw
    Contributor

    triage, no progress.

  8. pnkfelix commented on Nov 21, 2013

    @pnkfelix
    Contributor

    It would be good to decide what we're going to do about these things. (This and #8649)

    But its not clear whether it should block 1.0.

  9. nikomatsakis commented on Nov 25, 2013

    @nikomatsakis
    Contributor

    cc me

  10. ezyang commented on Dec 17, 2013

    @ezyang
    Contributor

    Maybe there could be a special case when the iterator expression is "simple" enough: a variable, struct field access, etc?

    It seems to me that there is precedent for language constructs to act specially when the expression in question is an lvalue, c.f. match; that would be a natural choice here.

  11. emberian commented on Jun 11, 2014

    @emberian
    Contributor

    Another painful example:

    fn main() {
        let s = "foobar bazbang";
        let mut chars = s.chars();
    
        loop {
            match chars.next() {
                Some(c) => {
                    if c == ' ' {
                        chars.next();
                    } else {
                        print!("{}", c);
                    }
                },
                None => { break; }
            }
        }
    }

    is good, but:

    fn main() {
        let s = "foobar bazbang";
        let mut chars = s.chars();
    
        for c in chars {
            if c == ' ' {
                chars.next();
            } else {
                print!("{}", c);
            }
        }
    }

    is not.

  12. bluss commented on Jun 11, 2014

    @bluss
    Contributor

    Is it possible to migrate to a for-by-value by recommending users who need the reference behaviour to use Iterator::by_ref() ?

  13. huonw commented on Jun 11, 2014

    @huonw
    Contributor

    @blake2-ppc could you write the expansion you are thinking of? (I'm specifically wondering how that would allow for something like for x in foo.iter() { ... }, while also allowing for x in bar { bar.next() } to do the right thing.)

  14. pczarn commented on Jul 30, 2014

    @pczarn
    Contributor

    Solved by #15809

  15. SimonSapin commented on Jul 30, 2014

    @SimonSapin
    ContributorAuthor

    @pczarn, indeed it is, thanks for the heads up! Updated for language/lib changes, the initial test case now works as expected:

    fn main() {
        let mut iter = range(1i, 10);
        for i in iter {
            println!("{}", i);
            println!("{}", iter.next());
        }
    }
    1
    Some(2)
    3
    Some(4)
    5
    Some(6)
    7
    Some(8)
    9
    None
    
  16. 23 remaining items

  17. Stebalien commented on Dec 18, 2014

    @Stebalien
    Contributor

    You can now just use while let:

    while let Some(item) = iterator.next() {
        // ...
    }

    It's not perfect but but better than using a macro (IMHO).

  18. SimonSapin commented on Dec 18, 2014

    @SimonSapin
    ContributorAuthor

    Oooh, this is much nicer indeed. In particular since there is only one nesting level of {}. Thanks for the tip!

  19. smosher commented on Dec 19, 2014

    @smosher

    As far as blocking 1.0 goes, it won't bother me personally if this doesn't get fixed until after. But it might be confusing and offputting to newcomers who already have a lot to learn about ownership. (I know I've invented a few new things to say after "why the ...?" beacuse of this issue.)

    Might it be possible to detect the situation and emit a hint about the workaround?

  20. nikomatsakis commented on Dec 21, 2014

    @nikomatsakis
    Contributor

    Now that while let is an option, I am not sure if this should be
    fixed. Particularly since for was defined to invoke IntoIterator
    on its argument -- and thus in a sense takes ownership of its argument
    (though not quite, is that a backwards compat hazard @aturon?). If you
    plan to mess with the iterator itself during iteration, it seems to me
    that using a distinct kind of loop is a good indication of this (I
    certainly don't expect it when I see a for loop).

    On Thu, Dec 18, 2014 at 07:46:54PM -0800, Stephen Mosher wrote:

    As far as blocking 1.0 goes, it won't bother me personally if this doesn't get fixed until after. But it might be confusing and offputting to newcomers who already have a lot to learn about ownership. (I know I've invented a few new things to say after "why the ...?" beacuse of this issue.)

    Might it be possible to detect the situation and emit a hint about the workaround?


    Reply to this email directly or view it on GitHub:
    #8372 (comment)

  21. emberian commented on Dec 21, 2014

    @emberian
    Contributor

    I used to be annoyed about this a ton, but now that we have while let,
    I agree.

    On Sat, Dec 20, 2014 at 9:08 PM, Niko Matsakis notifications@github.com
    wrote:

    Now that while let is an option, I am not sure if this should be
    fixed. Particularly since for was defined to invoke IntoIterator
    on its argument -- and thus in a sense takes ownership of its argument
    (though not quite, is that a backwards compat hazard @aturon?). If you
    plan to mess with the iterator itself during iteration, it seems to me
    that using a distinct kind of loop is a good indication of this (I
    certainly don't expect it when I see a for loop).

    On Thu, Dec 18, 2014 at 07:46:54PM -0800, Stephen Mosher wrote:

    As far as blocking 1.0 goes, it won't bother me personally if this
    doesn't get fixed until after. But it might be confusing and offputting to
    newcomers who already have a lot to learn about ownership. (I know I've
    invented a few new things to say after "why the ...?" beacuse of this
    issue.)

    Might it be possible to detect the situation and emit a hint about the
    workaround?


    Reply to this email directly or view it on GitHub:
    #8372 (comment)

    —
    Reply to this email directly or view it on GitHub
    #8372 (comment).

    http://octayn.net/

  22. SimonSapin commented on Dec 21, 2014

    @SimonSapin
    ContributorAuthor

    I still kinda wish this was fixed, but I understand @nikomatsakis’s argument and I don’t mind as much with the while let alternative.

  23. japaric commented on Jan 31, 2015

    @japaric
    Contributor

    #20790 implemented the new for-loops that take the iterator by value, so this issue can't be fixed (without changing the semantics in a backwards incompatible way). This should be closed.

  24. SimonSapin commented on Jan 31, 2015

    @SimonSapin
    ContributorAuthor

    Fair enough. I’m happy with the while let Some(value) = iterator.next() { trick.

  25. added a commit that references this issue on Feb 10, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P-lowLow priority

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions