Search code examples
rustmio

Rust + mio tcp client: explicit lifetime required in the type of connection


I want to implement tcp client using mio. This is my code:

pub struct Client<'a> {
    pub connected: bool,
    connection: Option<&'a TcpStream>,
    auth_handler: auth::AuthHandler<'a>,
}

impl Client<'_> {
    pub fn connect(&mut self, host: &str, port: i16) {
        let addr_list = format!("{}:{}", host, port)
            .to_socket_addrs()
            .unwrap();

        let addr = addr_list.last().unwrap();

        match TcpStream::connect(addr) {
            Ok(stream) => {
                self.connected = true;
                self.connection = Some(&stream);
                self.auth_handler.init(self.connection.unwrap());
                self.auth_handler.authenticate("login".to_string(), "password".to_string());
                println!("Connected to {}:{}", host, port);
            }
            Err(..) => {
                println!("Cannot connect !");
            }
        }
    }

    pub fn listen(&mut self) {
        let mut connection = self.connection.as_mut().unwrap();
        let mut poll = Poll::new().unwrap();
        let mut events = Events::with_capacity(256);

        poll.registry().register(
            connection,
            CLIENT,
            Interest::READABLE | Interest::WRITABLE
        );

        loop {
            poll.poll(&mut events, None).unwrap();

            for event in events.iter() {
                match event.token() {
                    CLIENT => {
                        if event.is_writable() {
                            // ...
                        }

                        if event.is_readable() {
                            println!("Data")
                        }
                    },
                    _ => (),
                }
            }
        }
    }

    pub fn new<'a>() -> Client<'a> {
        Client {
            connected: false,
            connection: None,
            auth_handler: auth::AuthHandler {
                connection: None::<&'a TcpStream>,
            },
        }
    }
}

and code of my auth handler:

pub struct AuthHandler<'a> {
    pub connection: Option<&'a TcpStream>,
}

impl AuthHandler {
    pub fn authenticate(&self, login: String, password: String) {
        // ...
    }

    pub fn new<'a>() -> AuthHandler<'a> {
        AuthHandler {
            connection: None::<&'a TcpStream>,
        }
    }

    pub fn init(&mut self, connection: &TcpStream) {
        self.connection = Some(&connection);  // error here see ERRORS below
    }
}

on compile I got an error "error[E0621]: explicit lifetime required in the type of connection":

self.connection = Some(&connection);
                  ^^^^^^^^^^^^^^^^^ lifetime `'static` required

how to fix it ? From my side I am not sure if static lifetime is OK since I want to destroy connection once authenticated and logged in.


Solution

  • The lifetime of connection is really 'a, but from the code you've posted nobody owns the TcpStream. You could fix the specific error you're having by using the AuthHandlers lifetime:

    impl<'a> AuthHandler<'a> {
        pub fn authenticate(&self, login: String, password: String) {
            // ...
        }
    
        pub fn new() -> AuthHandler {
            AuthHandler {
                connection: None,
            }
        }
    
        pub fn init(&mut self, connection: &'a TcpStream) {
            self.connection = Some(connection);
        }
    }
    

    But I would expect that you would then get other errors, because in effect Client is a self-referential struct.

    So one way to share the connection between these objects would be to put it inside a Arc<Mutex<TcpStream>> and share that between your two objects:

    pub struct Client<'a> {
        pub connected: bool,
        connection: Option<Arc<Mutex<TcpStream>>>,
        auth_handler: auth::AuthHandler<'a>,
    }
    
    pub struct AuthHandler {
        pub connection: Option<Arc<Mutex<TcpStream>>>,
    }
    

    then the TcpStream will stay alive as long as you need it. Of course, you would need to .lock() it whenever you use it.

    Another option would be to only pass the TcpStream to AuthHandler when it uses it:

    pub struct AuthHandler;
    
    impl AuthHandler {
        pub fn authenticate(&self, login: String, password: String, stream: &TcpStream) {
            // ...
        }
    }