Search code examples
rustborrow-checkermio

How to save reference to handler in hashmap field


I am learning Rust and trying to write a websocket server. The logic is following: WSConnectionFactory creates WSHandler which handle incoming messages and send them to other clients according to arbitrary rules. The problem is that I don't know how to implement such behavior.

Limitation: I can't change signature of Factory and Handler traits because they are provided by ws-rs library.

Question: How to implement this using RefCell/Cell?

extern crate rand;
extern crate rustc_serialize;
extern crate ws;
#[macro_use]
extern crate log;
#[macro_use]
extern crate env_logger;

use std::cell::RefCell;
use std::collections::HashMap;
use rand::random;
use ws::{Factory, Sender, Handler, Handshake, Message, CloseCode, WebSocket};
use ws::Result as WSResult;
use ws::util::Token;

struct WSConnectionFactory<'p> {
    handlers: HashMap<&'p u32, RefCell<&'p WSHandler<'p>>>,
}

#[derive(Debug)]
struct WSHandler<'h> {
    uid: &'h u32,
    ws: RefCell<&'h Sender>,
}

impl<'p> Factory for WSConnectionFactory<'p> {
    type Handler = WSHandler<'p>;

    fn connection_made(&mut self, ws: Sender) -> Self::Handler {
        println!("factory.connection_made token={:?}", &ws.token());
        let uid = &random::<u32>();
        let handler = WSHandler {
            uid: uid,
            ws: RefCell::new(&ws),
        };
        self.handlers.insert(uid, RefCell::new(&handler));
        handler
    }
}

impl<'h> Handler for WSHandler<'h> {
    fn on_open(&mut self, _handshake: Handshake) -> WSResult<()> {
        println!("handler.on_open");
        Ok(())
    }
    fn on_message(&mut self, msg: Message) -> WSResult<()> {
        println!("handler.on_message {:?}", msg);
        Ok(())
    }
    fn on_timeout(&mut self, _token: Token) -> WSResult<()> {
        println!("handler.on_timeout {:?}", _token);
        Ok(())
    }
    fn on_close(&mut self, code: CloseCode, reason: &str) {
        println!("handler.on_close code={:?}, reason={:?}", code, reason);
    }
}

fn main() {
    let factory = WSConnectionFactory { handlers: HashMap::new() };
    let ws_socket = WebSocket::new(factory).expect("Can't create WebSocket");
    ws_socket.listen("127.0.0.1:8080").expect("Can't bind to socket");
}

Solution

  • You are trying to return a WSHandler from connection_made while also storing a reference to the WSHandler in the WSConnectionFactory struct. This is impossible (with borrowed pointers), because by returning a WSHandler, you have no control on what will happen to it (it could be moved or dropped, which would invalidate your pointer). You are also storing borrowed pointers when you should just be storing values directly.

    WSConnectionFactory creates WSHandler which handle incoming messages and send them to other clients according to arbitrary rules.

    If you want to send messages to other clients, you actually need a Sender, not a WSHandler. Thankfully, Sender implements Clone, and from looking quickly through the code, cloning a Sender should give you a second "handle" to the same endpoint. Therefore, you should put a Sender in your HashMap, not a WSHandler.

    extern crate rand;
    extern crate rustc_serialize;
    extern crate ws;
    #[macro_use]
    extern crate log;
    #[macro_use]
    extern crate env_logger;
    
    use std::collections::HashMap;
    use rand::random;
    use ws::{Factory, Sender, Handler, Handshake, Message, CloseCode, WebSocket};
    use ws::Result as WSResult;
    use ws::util::Token;
    
    struct WSConnectionFactory {
        handlers: HashMap<u32, Sender>,
    }
    
    #[derive(Debug)]
    struct WSHandler {
        uid: u32,
        ws: Sender,
    }
    
    impl Factory for WSConnectionFactory {
        type Handler = WSHandler;
    
        fn connection_made(&mut self, ws: Sender) -> Self::Handler {
            println!("factory.connection_made token={:?}", &ws.token());
            let uid = random::<u32>();
            let handler = WSHandler {
                uid: uid,
                ws: ws.clone(),
            };
            self.handlers.insert(uid, ws);
            handler
        }
    }
    
    impl Handler for WSHandler {
        fn on_open(&mut self, _handshake: Handshake) -> WSResult<()> {
            println!("handler.on_open");
            Ok(())
        }
        fn on_message(&mut self, msg: Message) -> WSResult<()> {
            println!("handler.on_message {:?}", msg);
            Ok(())
        }
        fn on_timeout(&mut self, _token: Token) -> WSResult<()> {
            println!("handler.on_timeout {:?}", _token);
            Ok(())
        }
        fn on_close(&mut self, code: CloseCode, reason: &str) {
            println!("handler.on_close code={:?}, reason={:?}", code, reason);
        }
    }
    
    fn main() {
        let factory = WSConnectionFactory { handlers: HashMap::new() };
        let ws_socket = WebSocket::new(factory).expect("Can't create WebSocket");
        ws_socket.listen("127.0.0.1:8080").expect("Can't bind to socket");
    }