Search code examples
rustownership

Why does this struct method still borrow the mutable reference?


I am attempting to implement something akin to the Observer pattern in Rust. I have some decent experience with Rust, however I can't specifically pinpoint the reason why the following code has this compilation problem. Any help/explanation will be greatly appreciated.

Context: Essentially, I have a vector of states in one struct (the subscriber) and need to "connect" it to another struct (the publisher). The publisher has a mutable reference to a specific index of the subscriber's state vector.

I have created a very crude and simplified version of the type of logic I need:

use std::cell::{Cell};


#[derive(Debug)]
pub struct Entity<'a> {
        pub states: Vec<Cell<bool>>,
        pub subscribers: Vec<&'a Cell<bool>>
    }
    
    impl <'a>Entity<'a> {
        pub fn new(size: usize) -> Self {
            Entity {
                states: vec![Cell::new(false); size],
                subscribers: Vec::new()
            }
        }
        
        pub fn connect<'b: 'a>(publisher: &'a mut Entity<'a>, subscriber: &'b mut Entity<'b>) {
            for state in subscriber.states.iter() {
                publisher.subscribers.push(state);
            }
        }
        
        pub fn notify(&mut self) {
            for subscriber in self.subscribers.iter() {
                subscriber.replace(true);
            }
        }
    }


fn main() {
    let mut pub_ = Entity::new(3);
    let mut sub_ = Entity::new(3);
    
    // view initial states of subscriber
    println!("{:?}", sub_);
    
    Entity::connect(&mut pub_, &mut sub_);
    
    // notify the subscriber (change all states)
    pub_.notify();
    
    // view final states of subscriber
    println!("{:?}", sub_);
}

However, this yields the following compiler error:

error[E0499]: cannot borrow `pub_` as mutable more than once at a time
  --> src/main.rs:44:5
   |
41 |     Entity::connect(&mut pub_, &mut sub_);
   |                     --------- first mutable borrow occurs here
...
44 |     pub_.notify();
   |     ^^^^^^^^^^^^^
   |     |
   |     second mutable borrow occurs here
   |     first borrow later used here

error[E0502]: cannot borrow `sub_` as immutable because it is also borrowed as mutable
  --> src/main.rs:47:22
   |
41 |     Entity::connect(&mut pub_, &mut sub_);
   |                                --------- mutable borrow occurs here
...
47 |     println!("{:?}", sub_);
   |                      ^^^^
   |                      |
   |                      immutable borrow occurs here
   |                      mutable borrow later used here
   |

I expect this. My understanding is that since I use the same lifetime parameter 'a, it expects the mutable reference to live exactly as long as Entity<'a> so of course it won't be relinquished until the end of 'a. Obviously, I also realize that the subscriber must live at least as long as the publisher since the publisher holds references to the subscriber (and is set to live as long as those references).

My confusion comes from if I attempt something like the following:

impl <'a, 'b: 'a, 'c>Entity<'a> {
        pub fn new(size: usize) -> Self {
            Entity {
                states: vec![Cell::new(false); size],
                subscribers: Vec::new()
            }
        }
        
        pub fn connect(publisher: &'c mut Entity<'a>, subscriber: &'c mut Entity<'b>) {
            for state in subscriber.states.iter() {
                publisher.subscribers.push(state);
            }
        }
        
        pub fn notify(&mut self) {
            for subscriber in self.subscribers.iter() {
                subscriber.replace(true);
            }
        }
}
error: lifetime may not live long enough
  --> src/main.rs:22:17
   |
12 | impl <'a, 'b: 'a, 'c>Entity<'a> {
   |       --          -- lifetime `'c` defined here
   |       |
   |       lifetime `'a` defined here
...
22 |                 publisher.subscribers.push(state);
   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ argument requires that `'c` must outlive `'a`

In this case, why must the mutable reference live longer than the Entity? How come the mutable reference isn't dropped after Entity::connect(&mut pub_, &mut sub_); and how can I make it so?

I stripped it down even more and the following code works exactly as I would have figured:

use std::cell::{Cell};


pub fn connect<'a, 'b: 'a>(publisher: &mut Vec<&'a Cell<bool>>, subscriber: &'b mut Vec<Cell<bool>>) {
    for state in subscriber.iter() {
        publisher.push(state);
    }
}

pub fn notify<'a>(publisher: &mut Vec<&'a Cell<bool>>) {
    for state in publisher.iter() {
        state.replace(true);
    }
}

fn main() {
    let mut pub_: Vec<&Cell<bool>> = Vec::new();
    let mut sub_ = vec![Cell::new(false); 3];
    
    // inital subscriber states
    println!("{:?}", sub_);
    
    // give the publisher references to the subscriber states
    connect(&mut pub_, &mut sub_);
    
    // make the publisher notify all subscribers (change states)
    notify(&mut pub_);
    
    // final subscriber states
    println!("{:?}", sub_);
}
[Cell { value: false }, Cell { value: false }, Cell { value: false }]
[Cell { value: true }, Cell { value: true }, Cell { value: true }]

Additional:

  • I did manage to get a version of this to work but it is not as clean as I would like it. Instead of passing two mutable references, I pass an immutable reference (for the subscriber) and allow the method to take complete ownership of the publisher. I then return the publisher when I am done. I am not satisfied with this solution.

  • Yes, I know I can implement this with callback functions (I already have). I am trying to avoid that method of implementation for something like this. There are other resources that exist showcasing the Observer pattern in Rust, they have been helpful but don't particularly fit my intended use case.


Solution

  • Here's a fixed version:

    pub fn connect<'b: 'a>(publisher: &mut Entity<'a>, subscriber: &'a mut Entity<'b>) {
        for state in subscriber.states.iter() {
            publisher.subscribers.push(state);
        }
    }
    

    Note that I removed the explicit lifetime entirely from the publisher reference, and that I matched the lifetime inside the publisher with the reference to the subscriber.

    If you see a &'a mut Thing<'a> it usually means that something is wrong, because it means that the references in Thing and the reference to Thing have the same lifetime, which is usually impossible, since those must have existed in order to create Thing in the first place.