Cannot infer appropriate lifetime for mutable reference


#1

The compiler complains about lifetimes within my mutable variant of a method, while the immutable version compiles just fine. The reproduction can be found here. Apologies if this reproduction is overwhelming, i tried to boil it down as much as I could while preserving the structure of my current code.

First some context, I’m trying to do two things;

  1. Create an adapter around defined services through (temporary) composition, like a ZoneAdapter around EntityService. The ZoneAdapter contains references to both Zone information and the EntityService. Functionality is defined onto the Adapter and utilises both references to provide some higher-level functionality. The intention of ZoneAdapter is to attach/manipulate Zone-information (when playing a board-game, all Entities in your hand are placed within the HandZone), without pushing that responsibility onto the EntityService.

  2. Abstract mutable and immutable references by using generics, this is what MutSwitch does. The intention is to have to create only one Adapter structure for both mutable and immutable operations. These operations are then specialized in accordance with generic arguments.
    This results in 1 structure and 2 implementation blocks instead of 2 structs + 2 implementation blocks.

To surface the compiler error you can uncomment lines 186-197. I’m unsure how to solve this.
I’m also interested in what the compiler sees differently between the immutable and mutable version of ZoneAdapter::iter_zone, which causes the failed compilation.

From what I understand the lifetime of the returned mutable Entity-reference cannot be inferred because it cannot outlive the borrow of entity_service(r188) and transitively the borrow of self(r187), while the'a lifetime attached to MutSwitch(r162) actually outlives that borrow of self(r187).
Manually applying the lifetime 'a to self and the return type(r187) does not help here. I suppose a generic lifetime parameter is necessary on one of the structures to make this work?

This is the first time I’m trying to create wrapper structures for lifetimes so something could be missing or my comprehension of it still lacks. I’d love to hear from other people who tried to tackle similar issues (and their conclusions) as well.

Thank you for reading!


#3

I think this is essentially the same thing as:

struct S<T> {
    vals: Vec<T>,
}

impl<T> S<T> {
    fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
        IterMut { s: self }
    }

    fn iter(&self) -> impl Iterator<Item = &T> {
        Iter { s: self }
    }
}

struct IterMut<'a, T: 'a> {
    s: &'a mut S<T>,
}

impl<'a, T> Iterator for IterMut<'a, T> {
    type Item = &'a mut T;

    fn next(&mut self) -> Option<Self::Item> {
        // THIS DOES NOT COMPILE!
        Some(&mut self.s.vals[0])
    }
}

struct Iter<'a, T: 'a> {
    s: &'a S<T>,
}

impl<'a, T> Iterator for Iter<'a, T> {
    type Item = &'a T;

    fn next(&mut self) -> Option<Self::Item> {
        Some(&self.s.vals[0])
    }
}

The gist is when you hold a &'a mut T reference, you cannot give it out from a mutable of borrow of self for longer than you hold the mutable borrow of self. In other words:

struct Foo<'a>(&'a mut String);

impl<'a> Foo<'a> {
    fn method(&mut self) -> &'a mut String { ... }
}

This doesn’t work either for the same reason - mutable references are not Copy, but move-only. This is different from immutable references, where you’re free to copy them around as much as you want. If the above was allowed, then someone can call method multiple times and obtain a (potentially) mutable reference to the same value, violating aliasing constraints.

Iterator is notoriously difficult to implement for mutable references. At the lowest layer, you’d need unsafe code because there’s no way to convince the compiler that you’re returning a different mutable reference from each iteration of the loop.

Search for “streaming iterator” for a different approach to the problem.


#4

Rust doesn’t really support abstracting over mutability with generics. That’s why you have a ton of separate _mut() methods in std.

Usually alternative approaches are used, e.g.

  • RefCell for interior mutability allows you to use “immutable” & everywhere in your interface.
  • Instead of giving &mut reference as a return type, accept a closure and give &mut to the closure. This limits the scope of the lifetime and it’s easier to reason about (both for humans and the compiler).

#5

The language has lots of complicated to use code in slice because of the unsafe nature.

Possible unsafe function for what you want;

    pub fn iter_zone<'b>(&'b mut self, zone: ZE) -> impl Iterator<Item = &'a mut E> + 'b {
        ...
        // e_id below must never be same in map or will be giving aliased mutable references
        .map(move |e_id| unsafe { &mut *(entity_service.get_mut(*e_id).unwrap() as *mut E) })
    }

#6

(Replying to my own)
What I wrote would allow broken (unsafe to use) code. For the life of the iterator the structure would be mutably borrowed (safe if comment met,) but items the iterator gives have longer life ('a is longer than 'b) so can outlive the iterator. Hence you could keep one and reuse the function to get new iterator; which then would give alias for same item.


#7

About the mutable borrows being move-only; How can I know if a mutable borrow re-borrowed or moved, looking at that example code? (I read your comment on another thread)
I suppose it’s moved into the struct and re-borrowed as argument for method method.
I think re-borrow happens when a new scope is introduced, so that would be lifetime coercion while moving keeps the original declared lifetime?

The streaming iterator concept only works when you own the data, so it’s not applicable to the Adapter concept itself (afaik). The lack of examples within the docs makes it difficult to understand what i can get out of it.
Would returning a streaming iterator from EntityService be a correct approach? The iterator borrows the storage object (eg: HashMap) from the Service and yields mutable borrows for each Entity within that storage object.


#8

Thank you for the mentioned solution.
I guess unsafe is the only option left unless I redesign my approach.

Do you happen to know of ongoing discussions (and where i can find these) on the issue of returning outliving items? I want to stay updated to relevant updates.


#9

You cannot move out of borrowed content, and so method() would indeed need to reborrow because self is borrowed. Here’s an example with more explicit method names:

struct Foo<'a>(&'a mut String);

impl<'a> Foo<'a> {
    fn reborrow(&mut self) -> &mut String { 
        self.0
    }
    
    // self is consumed, and we move the 'a lifetimed borrow out of it
    fn moving(self) -> &'a mut String {
        self.0
    }
}

Streaming iterator doesn’t need owned data. In the example above, reborrow method lends a temporary borrow, tied to the borrow of self, to the caller - that’s what a streaming iterator needs. Unfortunately, in the Iterator trait, there’s no way to associate the Item associated type to a borrow of self in next() - this would require generic associated types, and a change to the Iterator trait (or more likely, a new trait).