Help needed on shared references between structs

Hello everyone.

I am in need of help regarding lifetimes (or software design?) in structs. I am trying to code a "simple" chat server in order to learn rust. I already understood so much! The design is a classic un-optimized 1 client = 1 server thread, with 2 other server threads: the client acceptance loop, and the Message dispatching loop. I will investigate the more standard select(2) event loop later on. Baby steps.

// Message flow
[client_app] -> [client server thread] -> (via shared sync::mpsc::Sender<Message>) -> [dispatch server loop] -> (via User's sync::mpsc::Sender<Message>) -> [client server thread] -> [client_app]

However, i have hit a stop as i am faced with the following situation:

Considering the following structs:

use std::sync::Mutex;
use std::sync::mpsc::Sender;

/// Represent a user, server side
struct User {
    name: [u8; 32],
    client_tx: Mutex<Sender<Message>>, // a Message is simply 3 [u8] arrays: source, target, content
}

/// Represents a channel, with its members.
/// I had hoped to just store references to the actual User(s), which are stored in the Server struct
struct Channel<'a> {
    name: [u8; 32],
    members: Vec<&'a User>,
}

struct Server<'a> {
    channels: Vec<Channel<'a>>,
    users: Vec<User>,
}

NOTE: I first thought of the actual User storage in the server in order to enable Users to join multiple channels. I realize that there's no way for rust to ensure the proper removal of all the Channel's references on client deletion, hence my doubts regarding my approach.

however, when trying to write the code enabling a User to join a Channel, i am getting lifetime errors:

impl Server<'_> {
    fn add_user_to_channel(&mut self, user_name: &str, channel_name: &str) {
        let user = self
            .users
            .iter()
            .find(|u| u.name == user_name.as_bytes())
            .expect("Received message from user not present in server's user table...");
        let channel = self
            .channels
            .iter_mut()
            .find(|&&mut c| c.name == channel_name.as_bytes());
        if channel.is_some() {
            channel.unwrap().members.push(user);
        }
    }
}

I am getting the following errors (line numbers not accurate, the code has been purged of non-essential info):

  --> src/server.rs:48:14
   |
48 |             .iter()
   |              ^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 45:5...
  --> src/server.rs:45:5
   |
45 | /     fn add_user_to_channel(&mut self, user_name: &str, channel_name: &str) {
46 | |         let user = self
47 | |             .users
48 | |             .iter()
...  |
57 | |         }
58 | |     }
   | |_____^
note: ...so that reference does not outlive borrowed content
  --> src/server.rs:46:20
   |
46 |           let user = self
   |  ____________________^
47 | |             .users
   | |__________________^
note: but, the lifetime must be valid for the lifetime `'_` as defined on the impl at 44:13...
  --> src/server.rs:44:13
   |
44 | impl Server<'_> {
   |             ^^
note: ...so that reference does not outlive borrowed content
  --> src/server.rs:56:43
   |
56 |             channel.unwrap().members.push(user);
   |                                           ^^^^

So i was wondering: is there a way to tell rustc that the channel.members references will be deleted before the real server.user ? Or am i going entirely in the wrong direction?

I thank you for reading this far, and apologize if things aren't clear enough. Please tell me if I can provide any more information or if sections need cleaning.

paul

You need to use shared owned references: Arc<User>.

A temporary borrow (&) in a struct makes your structs temporary themselves, tied to a single scope, which makes them unusable for things that need to survive longer than a single function call.

Also borrows in structs can't possibly borrow from the struct itself. Self-referential structs are not safe in borrow checker's memory model, and the compiler will send you in circles complaining about all the lifetimes.

Arc lets you have any number of references wherever you want. You need to be mindful about reference cycles — perhaps make some of them into Weak references.

1 Like

Hi @kornel.

You were right, everything worked well after changing the structs & Server.add_user_to_channel to the following:

struct User {
    name: [u8; 32],
    client_tx: Mutex<Sender<Message>>,
}

struct Channel {
    name: [u8; 32],
    members: Vec<Arc<User>>,
}

struct Server {
    channels: Vec<Channel>,
    users: Vec<Arc<User>>,
}

impl Server {
    fn add_user_to_channel(&mut self, user_name: &str, channel_name: &str) {
        let user = self
            .users
            .iter()
            .find(|u| u.name == user_name.as_bytes())
            .expect("Received message from user not present in server's user table...");
        let channel = self
            .channels
            .iter_mut()
            .find(|c| c.name == channel_name.as_bytes());
        if channel.is_some() {
            channel.unwrap().members.push(Arc::clone(user));
        }
    }
}

// function handling individual client connections
fn handle_connection(
    server: Arc<Mutex<Server>>,
    stream: &mut TcpStream,
    tx: Arc<Mutex<Sender<Message>>>,
){
//  --- 8< snip snip 8<---
    server
        .lock()
        .expect("could not lock server while trying to add user to list")
        .users
        .push(Arc::new(user));
//  --- 8< snip snip 8<---

I thank you for your input, the compiler stop complaining, and i can go on to write the much needed tests.

paul

2 Likes

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.