Mixed up borrowing

Hi. I'm new at Rust and follow the docs to learn about it. Recently I read about ownership and borrowing and when I tried to implement a simple logic I had some problems. I hope someone helps me to understand what's going on. This is my code:

struct User<'a> {
    username: String,
    following: Vec<&'a User<'a>>,
    follower: Vec<&'a User<'a>>,
} 
impl<'a> User<'a> {
    fn send_post(&self, post: &str){
        for user in &self.follower{
            // do somtihng
        }
    }
    fn follow(&'a mut self, user: &'a User<'a>) {
        self.following.push(user);
    }
}
fn main() {
    let mut user1 = User{username: "user1".to_string(), follower: vec![], following: vec![]};    
    let mut user2 = User{username: "user2".to_string(), follower: vec![], following: vec![]};
    user1.follow(&user2);
    // user2.follow(&user1);
}

I want to add each user to following and follower list without using copy or clone. Is it possible? the main problem that I had was using mutable reference twice or using immutable and mutable reference at a time. How can I implement this logic?

I'm a bit short on time, so can't put together a good explanation. Working with circular references like you have is tricky. It won't work at all with &mut, which requires there to be no other references to the same object.

You can do this with some interior mutability, though:

use std::cell::RefCell;

struct User<'a> {
    username: String,
    following: RefCell<Vec<&'a User<'a>>>,
    follower: RefCell<Vec<&'a User<'a>>>,
}
impl<'a> User<'a> {
    fn new(username: String) -> Self {
        User {
            username,
            following: RefCell::new(vec![]),
            follower: RefCell::new(vec![]),
        }
    }
    fn send_post(&self, post: &str) {
        for user in self.follower.borrow().iter() {
            // do somtihng
        }
    }
    fn follow(&'a self, user: &'a User<'a>) {
        self.following.borrow_mut().push(user);
        user.follower.borrow_mut().push(self);
    }
}
fn main() {
    let mut user1 = User::new("user1".to_string());
    let mut user2 = User::new("user2".to_string());
    user1.follow(&user2);
    user2.follow(&user1);
}

(Playground)

1 Like

thanks for your help

You should use Arc<Mutex<User>>, which is an equivalent of what User would be in Java/Python/JS and other GC languages.

Anything with &'a will make you stuck and hate Rust. Don't put references in structs until you have at least a year experience with Rust. I'm serious. It's an advanced feature that doesn't do what new users think it does.

& is not a general-purpose reference to another object. It is specifically a temporary borrow of an object, limited to a scope, and forces its target to be immutable.

& implies these objects are stored elsewhere, and the web of temporary mutual borrows you make will force them all to be immutable, and impossible to free/delete other than all of them at once (e.g. you'd have to use a memory pool/arena that outlives the entire graph).

OTOH if you use type that isn't a temporary view, you will be able to store User objects wherever you want, create and delete them, pass them around freely.

7 Likes

follow takes a &'a mut User<'a> (notice the 'a in both the reference and the type), which is never what you want. This will require you to have a reference that exists for at least as long as the pointed data do, thus locking your struct in place forever.

Also be careful with that &'a User<'a>, depending on how User is defined it could bring the same problems as &'a mut User<'a>.

When learning rust you should be careful when describing something as "simple logic". It may work easily in your mind, however you'll often find that it lacks clear ownership semantics.

1 Like

Fwiw, here's how I might approach this using a "poor man's arena" approach, keeping all the users in a Vec and passing around indices instead of references: playground. Happy to explain the design a bit more if something about it is confusing.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.