Keeping a list of structs while also returning them + misc questions

I want to implement a custom UDP based protocol. However, I am running into problems implementing it with a good API. Here’s a short example of approximately how I’d like it to work:

let host = Host::new(/* protocol config, port number to listen on etc. */)
let connection = host.connect(address, /* connection config */)
connection.send(bytes)

I’d like the connect function to return a Connection object that can be used to send packets. Because the Host owns the socket however, the sending and receiving has to be done via the Host:

let msg = host.service()  // Do IO stuff

Here’s what I tried (this is the simplest version that represents the problem):

struct Connection {
    value: u32  // some placeholder
}

struct Host {
    connections: Vec<Connection>
}

impl Host {
    fn new() -> Host {
        Host {
            connections: Vec::new()
        }
    }
    
    fn connect(&mut self, value: u32) -> &mut Connection {
        self.connections.push(Connection { value });
        let l = self.connections.len();
        self.connections.get_mut(l-1).unwrap()  // very hacky
    }
    
    fn service(&self){
        // do something with the connections
    }
}

fn main() {
    let mut host = Host::new();
    let conn = host.connect(1);
    // in this specific case, I could just drop `conn` right here.
    // However, in reality, I will want it to stick around
    // for much longer, across .service() calls
    host.service();
}

(Playground)

As you can see, there are a number of issues:

  • I have a situation where two things need a mutable borrow at the same time. I have tried solving this with Rc, but it does not really solve the “double borrow” problem.
  • adding something to a list while keeping a reference safely without resorting to the .length() hack

In your example, if you modify connect() to not return the Connection, then the code will compile. If the Connection is owned by Host, then it’s not going anywhere until host goes out of scope.

Or are you saying you would prefer to send through the Connection directly rather than going through Host?

Yep, exactly. Otherwise I’d run into a number of issues, like needing to share the Host everywhere, needing some way of specifying which connection you actually want to send on, some way to match that up with the incoming packets etc.

I think it might be possible to have the connection object just be a wrapper and make it store some kind of connection ID instead? Could that be a good way to go?

I would say you’re better off properly sharing the Connections where they’re needed.

I think that wrapping the Connection in `Arc<Mutex<>> will work. Similar to this playground

That manages to use mutable and immutable versions of the Connection from direct calls on Connection and through Host. Does that get what you need?

There might be a better solution if your code will be single-threaded.

See https://doc.rust-lang.org/book/2018-edition/ch16-03-shared-state.html for more info about Mutex