How to run a method that requires a mutable borrow of self and pass it non-mutable references of self fields

Beginner Question:
I am trying to call a method on a struct which requires a mutable borrow of self. However I would like to pass this method arguments that need a non-mutable borrow of self's fields. I'm sure what I'm trying to do is pretty stupid. I need some guidance on how to go about doing what I need.

Any help is greatly appreciated!

Link to playground with a minimal amount of code to reproduce the problem!

Corresponding Code Block:

struct Value {
    val: i32,
}
struct ReferencePair<'a> {
    ref_1: &'a Value,
    ref_2: &'a Value,
}
struct ReferencePairList<'a> {
    vals: Vec<Value>,
    refs: Vec<ReferencePair<'a>>,
}
impl<'a> ReferencePairList<'a> {
    fn add_ref_pair(&mut self, ref_1: &'a Value, ref_2: &'a Value) {
        let ref_pair = ReferencePair {ref_1, ref_2};
        self.refs.push(ref_pair);
    }
}

fn main() {
    let val_1 = Value {val: 1};
    let val_2 = Value {val: 2};
    let mut ref_list = ReferencePairList {
        vals: vec![val_1, val_2],
        refs: Vec::new(),
    };
    ref_list.add_ref_pair(&ref_list.vals[0], &ref_list.vals[1]);
}

Error that I'm getting:

error[E0502]: cannot borrow `ref_list` as mutable because it is also borrowed as immutable
  --> src/main.rs:26:5
   |
26 |     ref_list.add_ref_pair(&ref_list.vals[0], &ref_list.vals[1]);
   |     ^^^^^^^^^------------^^-------------^^^^^^^^^^^^^^^^^^^^^^^
   |     |        |             |
   |     |        |             immutable borrow occurs here
   |     |        immutable borrow later used by call
   |     mutable borrow occurs here

When you write <'a> on a struct, you are telling the compiler that the struct contains references to things outside of the struct, and that it should use this lifetime to ensure those external references stay valid. However in your case the reference points to another field in the same struct, hence the compiler error.

@alice, thanks for your reply!

If I understand you correctly, ReferencePair needs to have an explicit lifetime parameter <'a> since it contains references to things that are outside of the struct, specifically the two references to Value.

However, since ReferencePairList doesnt contain any references outside of its own struct, it shouldn't need a lifetime parameter. Based on this understanding if I modify the definition of ReferencePairList to be the following:

struct ReferencePairList {
    vals: Vec<Value>,
    refs: Vec<ReferencePair>,
}

I get the following compiler error:

error[E0106]: missing lifetime specifier
  --> src/main.rs:10:15
   |
10 |     refs: Vec<ReferencePair>,
   |               ^^^^^^^^^^^^^ expected named lifetime parameter
   |
help: consider introducing a named lifetime parameter
   |
8  | struct ReferencePairList<'a> {
9  |     vals: Vec<Value>,
10 |     refs: Vec<ReferencePair<'a>>,

This seems to indicate that since the ReferencePair struct needs a lifetime parameter, then I must include that in this definition as well. This goes against my understanding. However, if I follow along with the compiler and modify ReferencePairList, I then get the following error:

error[E0726]: implicit elided lifetime not allowed here
  --> src/main.rs:12:6
   |
12 | impl ReferencePairList {
   |      ^^^^^^^^^^^^^^^^^- help: indicate the anonymous lifetime: `<'_>`

Which means I need to add a lifetime parameter to the impl block as well. Having done that I get the following:

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter `'a` due to conflicting requirements
  --> src/main.rs:14:24
   |
14 |         let ref_pair = ReferencePair {ref_1, ref_2};
   |                        ^^^^^^^^^^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #3 defined on the method body at 13:5...
  --> src/main.rs:13:5
   |
13 | /     fn add_ref_pair(&mut self, ref_1: &Value, ref_2: &Value) {
14 | |         let ref_pair = ReferencePair {ref_1, ref_2};
15 | |         self.refs.push(ref_pair);
16 | |     }
   | |_____^
note: ...so that reference does not outlive borrowed content
  --> src/main.rs:14:46
   |
14 |         let ref_pair = ReferencePair {ref_1, ref_2};
   |                                              ^^^^^
note: but, the lifetime must be valid for the lifetime `'_` as defined on the impl at 12:24...
  --> src/main.rs:12:24
   |
12 | impl ReferencePairList<'_> {
   |                        ^^
note: ...so that the expression is assignable
  --> src/main.rs:15:24
   |
15 |         self.refs.push(ref_pair);
   |                        ^^^^^^^^
   = note: expected  `ReferencePair<'_>`
              found  `ReferencePair<'_>`

And this is where I get stumped. Based on my understanding, this means that the compiler is not able to infer the lifetime of 'ref_pair', since it doesn't know the lifetime of ref_1 and ref_2. The only solution seems to be to explicitly mention the lifetime parameters to be equal to the lifetime of the ReferencePairList struct. Which takes me back to my original problem again!

I apologize if I have made an error in understanding what you meant. Could you please help in correcting my understanding of lifetimes in this context.

Link to the corresponding playground

The basic problem is that self-referential structs (i.e. those where fields reference eachother) are simply impossible with ordinary references. In your case, I would recommend to just copy the Value, as it is a simple integer, and the reference is immutable, so it couldn't be modified behind the reference anyway.

Ah! Do you mean I need to use smart pointers like Box or something similar to get around this problem?

In my actual problem Value is not just a simple i32, but having said that, it can be made to implement Copy. I guess I'll go that route.

Thanks @alice!

Smart pointers such as Rc or Arc would work. Box would not help.

I realize what I'm trying to do is essentially encode a graph and there are many ways to achieve that using crates designed for that specific case, like petgraph and graphlib. There are also solutions for this using arenas like typed-arena and generational-arena.

This question on StackOverflow has good answers relevant for this type of problem.

This chapter by Nick Cameron on Graphs and Arena Allocation covers the exact way to go about solving this using Rc as suggested by @alice

According to generational-arena:

In these situations, it is tempting to store objects in a Vec<T> and have them reference each other via their indices. No more borrow checker or ownership problems! Often, this solution is good enough.

For my specific case this is actually good enough. For completeness-sake I have modified the original code that I had posted to incorporate this and it compiles without any problems! Downside to this solution is that I have to manually keep track of the index values which is a small price to pay for the simplicity of the solution. It is available on the playground. for reference.

Thanks for pointing me in the right direction @alice! I learnt a lot from this exercise.

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