Help with iter_mut lifetimes


#1

I’m trying to make a simple container mutably iterable:

https://play.rust-lang.org/?gist=02f9daa77878140bd9e812f7d68b3e0c&version=stable&backtrace=0

I fail to understand the error.
How do I make this work?


#2

You’ll want to do this (note the explicit 'b lifetime parameter for the mutable borrow of self):

impl<'a> Data<'a> {
    fn iter_mut<'b>(&'b mut self) -> DataIterMut<'a, 'b> {
        DataIterMut {
            next: Some(self),
        }
    }
}

#3

Lovely, thank you!


#4

I have a more complex case.
https://play.rust-lang.org/?gist=a38177555fbf873b28d79fee66f82625&version=stable&backtrace=0

Rust complains around the split calls, but the problem is with the lifetime annotation I’m forced to add to the split function. Specifically, annotating DataList<'a>. If I don’t annotate it split doesn’t compile, with a weird error about conflicting lifetimes that are actually identical. If I annotate it I’m unable to call split more than once.


#5

The split you want is this:

fn split<'b, 'a: 'b>(dl: &'b mut DataList<'a>) {
    for d in dl.iter_mut() {}
}

'a outlives 'b is precisely the relation that is natural if you have a &'b reference to a type containing a 'a. The referenced outlives the reference.

More precise lifetime parameters for iter mut as well:

impl<'a> DataList<'a> {
    fn iter_mut<'b>(&'b mut self) -> DataIterMut<'b, 'a> {
        DataIterMut {
            next: Some(self),
        }
    }
}

for me it compiles with those two changes.


#6

Ah, I didn’t think of going back to fix iter_mut.
I confess that the reversed lifetimes were found through trial and error, I didn’t go back to the fundamentals.
Thanks.


#7

I’ve reworked my program to have a loop in the recursive function, and I’m unable to make that work.

https://play.rust-lang.org/?gist=4e6a81d3a89f4d78ba844e52ee1136c1

fn rec<'a>(mut dl: Option<Rc<DataList<'a>>>, count: u64) {
    let foo = &mut format!("{}", count)[..];
    for i in 0..2 {
        if count == 0 {
            return;
        }
        let dl1 = Some(Rc::new(DataList {
            node: Rc::new(RefCell::new(Data {
                foo,
            })),
            parent: dl.clone(),
        }));
        rec(dl1, count - 1);
    }
}

The general idea is that I have a struct I can’t pass around (the borrow checker would rightfully complain), so instead I’m extracting a reference to field foo, stacking it in a cons list, and passing the cons list around. The cons list doesn’t live long enough to borrow things twice (only for one iteration of the loop), but the borrow checker seems to disagree.


#8

There are two separate issues here: multiple mutable borrows and a general lifetime issue with foo.

As far as the compiler is concerned, there are multiple mutable borrows even if nothing “bad” would happen in the way the code is structured. It can’t prove that based on signatures alone and will not attempt to follow the loop trip count and recursion. It’s also a bit unclear why you need &'a mut str since you’re not modifying the string slice itself, but only changing where Data points. Maybe this is just because you’re using an example though.

The lifetime issue with foo is because its lifetime can never be as long as the lifetime parameter that the caller specifies. Your example uses 'a which is likely longer than necessary. But, even if you use a different lifetime (explicitly or via elision), the caller specifies that lifetime and your local will never outlive that.

You can perhaps use Cow<'a, str> to achieve what you want, and achieve some borrow reuse rather than allocating an owned value all the time.


#9

I think the problem in both cases is that the compiler wants the borrow of foo to last for 'a.
(Yes, the &mut borrow seems superfluous, it’s a stand-in for passing a field from an Entry when the entry has other fields that can’t be passed around because the rec method is using them. The field does get modified in various places in the complete program. A Cow wouldn’t work as the intent is to modify the original).

I’d like to separate 'a and 'b so that the new DataList instance has a head node that doesn’t live as long as the rest of the list, unfortunately this creates recursion (if you add a lifetime parameter to DataList, you must also find a way to fill it in the parent field) and the language probably can’t accommodate that.

It seems I’ll be forced to make the Entry field a RefCell. It adds overhead everywhere, but I’m really unable to figure out another solution.


#10

OK, using another RefCell + a bit of swapping simplifies things.
I’ll try something like this.


#11

You can resort to raw pointers, with all the hazards that come with them. But, you’d avoid the overhead.