4
votes

I've discovered Rust broke some old code and I was determined to fix it. Sadly, it appears that lifetime references have changed quite a bit and something weird is happening. Field Counter.data is irrelevant, it's just used to show that there is some data I use and it's reference to generic stuff.

Here is the code:

struct Counter <'c, T: 'c> {
    val: u32,
    data: &'c T
}

struct CounterIterator<'c, T:'c> {
    iter: &'c mut Counter<'c, T>,
    num: u32
}

impl<'r, T> Iterator<u32> for CounterIterator<'r, T> {
    fn next(&mut self) -> Option<u32> {
        if self.num  == 0 {
            None
        } else {
            self.num -= 1;
            self.iter.incr()
        }
    }
}

impl<'c, T> Counter <'c, T> {
    fn new(dataz: &'c T) -> Counter<'c, T> {
        Counter {
            val: 0u32,
            data: dataz
        }
    }

    fn incr(&mut self) -> Option<u32> {
        self.val += 1;
        println!("{}", self.val);
        Some(self.val)
    }

    fn iter(&'c mut self, num: u32) -> CounterIterator<'c, T> {
        CounterIterator {
            iter: self,
            num: num
        }
    }
}

fn main() {
    let val = 11u;
    let mut cnt = Counter::new(&val);
    for i in range(0,2u) {
        cnt.incr();
    }

    for i in cnt.iter(3) {

    }

    cnt.incr(); // <- commenting this out "fixes" the problem
    // Otherwise the error is 
    // note: previous borrow of `cnt` occurs here; the mutable borrow prevents 
    // subsequent moves, borrows, or modification of `cnt` until the borrow ends

}

What is the mistake here? How do I make this so 'Iterator' idiom ends borrow by the time it exits the loop, instead at the end of block its defined? Also, what do explicit life times T:'c do?

For record I'm trying to achieve Iterator API similar to str.chars() in usage. If there is a saner way to do it, please let me know.

1

1 Answers

2
votes

In fn iter, the &'c mut self ties the mutable borrow of self to the lifetime 'c of the Counter. By the time you call cnt.incr() you can't borrow cnt because.... cnt is still alive (so the &'c mut self is still borrowing it).

One possible way to make your code work is to move data in Counter instead of storing a borrowed reference to it, like this:

struct Counter <T> {
    val: u32,
    data: T
}

If you want to keep data as a reference, another alternative is introducing a second named lifetime to the CounterIterator struct, so that the mutable borrow to Counter can live shorter than Counter itself.

// we now have a shorter lifetime ('s) and a longer one ('l). This is 
// expressed to the compiler by the 'l: 's bound that says "'l lives 
// at least as long as 's"
struct CounterIterator<'s, 'l: 's, T: 'l> {
    iter: &'s mut Counter<'l, T>,
    num: u32
}

impl<'c,  T> Counter <'c, T> {
// ...
    // now the &mut self can live as long as CounterIterator, not necessarily as
    // long as Counter. This allows it to be released as soon as the iteration
    // is over and CounterIterator goes out of scope
    fn iter<'a>(&'a mut self, num: u32) -> CounterIterator<'a,'c, T> {
        CounterIterator {
            iter: self,
            num: num
        }
    }
}

For reference, since the language is still somewhat in flux:

$ rustc -v
rustc 0.12.0-pre-nightly (2e3858179 2014-09-03 00:51:00 +0000)