How to resolve this lifetime error message - Struct contains reference of struct with lifetime parameter?

I'm using Rust to build a POC for some optimizer. At some point, I'd like to cache data for performance reason. However, I ran into some puzzle with layers of references. Any suggestions will be greatly appreciated. I tried to clean up code as much as possible as follow. Rust playground "share" just spins forever, so no link yet. Link to playground Rust Playground

use core::cell::RefCell;
use std::rc::Rc;
use std::collections::HashSet;

struct Dao {
    d1: usize,
}

impl Dao {
    fn capabilities(&self) -> Capabilities {
        Capabilities{ d1: &self.d1 }
    }
}

#[derive(PartialEq, Eq, Hash)]
struct Capabilities<'a> {
    d1: &'a usize
}

struct CompositeDao<'a> {
    dao: Dao,
    cache: RefCell<HashSet<Rc<Capabilities<'a>>>>
}

impl<'a> CompositeDao<'a> {
    fn new(dao: Dao) -> Self {
        Self { dao, cache: RefCell::new(Default::default()) }
    }
    
    fn capabilities<'s: 'a>(&'s self) -> Rc<Capabilities<'a>> {
        self.cache.borrow_mut().insert(Rc::new(self.flow_capabilities()));
        self.cache.borrow().iter().next().unwrap().clone()
    }
    
    fn flow_capabilities(&self) -> Capabilities {
        self.dao.capabilities()
    }
}

struct Solver<'d, 'df: 'd> {
    dao: &'df mut CompositeDao<'d>,
}

struct Goal<'d, 'df: 'd> {
    dao: &'df CompositeDao<'d>,
}

impl<'d, 'df: 'd> Goal<'d, 'df> {
    fn new(dao: &'df CompositeDao<'d>) -> Self {
        Self { dao }
    }
    
    fn solve(&self) {
        let caps = self.dao.capabilities();
        unimplemented!()
    }
}

impl<'d, 'df: 'd> Solver<'d, 'df> {
    fn solve<'s: 'df>(&'s mut self) {
      {
        let mut goal = Goal::new(self.dao);
        goal.solve();
      }
      
      self.persist();

    }

    fn persist(&mut self) {
        unimplemented!()
    }
}

The following is error message:

 --> src/lib.rs:61:11
   |
61 |       let mut goal = Goal::new(self.dao);
   |           ----^^^^
   |           |
   |           help: remove this `mut`
   |
   = note: `#[warn(unused_mut)]` on by default

error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable
  --> src/lib.rs:65:9
   |
61 |       let mut goal = Goal::new(self.dao);
   |                                -------- immutable borrow occurs here
...
65 |         self.persist();
   |         ^^^^^-------^^
   |         |    |
   |         |    immutable borrow later used by call
   |         mutable borrow occurs here

error: aborting due to previous error

Another version of this is without lifetime bound 's: 'df for solve method. However, that led to another error like "conflict lifetime". I don't feel bound 's: 'df is appropriate given that feels self-conflicting.

It doesn't matter what bounds you use, you can't have a unique borrow of a struct the coexists with any other borrow.

fn solve<'s: 'df>(&'s mut self) {
    {
        let mut goal = Goal::new(self.dao);
        goal.solve();
    }
  self.persist();
}

What you're doing here is borrowing self.dao to create the goal, then you're trying to borrow it again in persist. Typically to do this, you need to separately borrow the disparate parts of your struct:

fn solve<'s: 'df>(&'s mut self) {
    {
        let mut goal = Goal::new(self.dao);
        goal.solve();
    }
    self.cache.persist(self.dao);
}

In this type, the Capabilities stored in the cache field contains a reference, and that reference is borrowed from the dao field in the same struct:

struct CompositeDao<'a> {
    dao: Dao,
    cache: RefCell<HashSet<Rc<Capabilities<'a>>>>
}

This is a problem because that reference must live at least as long as the struct it's stored in, and since it points back into that same struct (making it self-referential), this effectively locks the struct for its entire existence, making it impossible to get an exclusive (&mut) reference or otherwise mutate/move/drop the struct.

Instead of storing an &T reference inside of Capabilities, consider using Rc for all shared references that you need to store in long-lived structs. For example:

That's what I didn't quite get. Like what I emphasized here using a block to limit life of Goal, I would expect Rust understands that there is no mutable and regular references coexist at the same time. I suspect is the bound 's: 'df somehow implies such condition, but not able to wrap my head around to understand it.

Matt, I think you identified the issue, this might be what skysch was saying too but more specific. However, you link seems to be not reflecting proposed change. Just making sure I'm on the same page, are you suggesting to wrap referred data in Capabilties with Rc? In real code, the data referred is actually something residing internally in an in-memory data store fronted by Dao. If I'm not mistaken, this requires a deep wiring of this data to be managed by Rc. I see the point, and understand why Rust needs that to be safe, just a bit too much :).

Oops! I updated the playground link in my previous comment; it should work now.

Yes, the suggestion is to use Rc for any data that you want to store multiple pointers to. Or, clone the data itself if it's immutable and if the overhead of cloning ends up lower than the overhead of Rc.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.