简体   繁体   中英

Using trait methods in threads

Basically, I'm making a program that's listening to a bunch of ports and that handles incoming packets in different ways. I decide to bundle this code into a Trait:

use std::old_io::{TcpStream, TcpListener, Listener, Acceptor, EndOfFile, IoResult};
use std::thread::Thread;

trait Server {
    fn new(port: u16) -> Self;

    fn hostname(&self) -> &String;

    fn initialize(&self) {
        let acceptor = TcpListener::bind(self.hostname().as_slice()).listen().unwrap();
        Thread::spawn(move|| {
            let mut acceptor = acceptor;
            for incoming_stream in acceptor.incoming() {
                match incoming_stream {
                    Ok(stream) => {
                        self.handle_client(stream);
                    },
                    Err(ref e) if e.kind == EndOfFile => break,
                    Err(e) => panic!("Unexpected error: {}", e),
                }
            }
        });
    }

    fn handle_client(&self, stream: TcpStream) -> ();
}

pub struct InternodeServer {
    hostname: String,
}

impl Server for InternodeServer {
    fn new(port: u16) -> InternodeServer {
        let hostname = format!("127.0.0.1:{}", port);
        InternodeServer {
            hostname: hostname,
        }
    }

    fn hostname(&self) -> &String {
        &self.hostname
    }

    fn handle_client(&self, stream: TcpStream) {
        println!("Received connection");
        let mut stream = stream;
        let response = b"Hello\r\n";
        let _ = stream.write_all(response);
        let _ = stream.close_write();
    }
}

fn main() {
    let test_server = <InternodeServer as Server>::new(9337);
    test_server.initialize();
}

However, this code won't work because you can't send Self . This is the error I receive:

test.rs:11:9: 11:22 error: the trait `core::marker::Send` is not implemented for the type `Self` [E0277]
test.rs:11         Thread::spawn(move|| {
                   ^~~~~~~~~~~~~
test.rs:11:9: 11:22 note: `Self` cannot be sent between threads safely
test.rs:11         Thread::spawn(move|| {
                   ^~~~~~~~~~~~~

So I also tried making handle_client a static method to avoid self. To do this, I simply changed handle_client to:

fn handle_client(stream: TcpStream)

And referenced it by doing:

Server::handle_client(stream);

However, I can't reference InternodeServer's static methods from Server's initialize method. When compiling, I get an error like:

test.rs:16:25: 16:46 error: type annotations required: cannot resolve `_ : Server` [E0283]
test.rs:16                         Server::handle_client(stream);
                                   ^~~~~~~~~~~~~~~~~~~~~
test.rs:16:25: 16:46 note: required by `Server::handle_client`
test.rs:16                         Server::handle_client(stream);

Is there any way around this?

I don't think that rust will allow you to invoke object methods directly from other thread because "move" closures cannot borrow anything, only move.

So you have to use some kind of inter-thread communication tool, for example, channels:

use std::thread::Thread;
use std::sync::{Arc, Mutex};
use std::sync::mpsc::{channel, Sender, Receiver, RecvError};
use std::net::{TcpStream, TcpListener};
use std::io::{ErrorKind, Write};

trait Server {
    fn new(port: u16) -> Self;

    fn hostname(&self) -> &String;

    fn initialize(&mut self, _detached: bool) {
        let acceptor = TcpListener::bind(self.hostname().as_slice()).unwrap();
        let server_tx = self.make_pipe();
        Thread::spawn(move|| {
            for incoming_stream in acceptor.incoming() {
                match incoming_stream {
                    Ok(stream) => server_tx.send(Arc::new(Mutex::new(stream))).unwrap(),
                    Err(ref e) if e.kind() == ErrorKind::NotConnected => break,
                    Err(e) => panic!("Unexpected error: {}", e),
                }
            }
        });
    }

    fn handle_client(&self, stream: Arc<Mutex<TcpStream>>);
    fn make_pipe(&mut self) -> Sender<Arc<Mutex<TcpStream>>>;
    fn run(&self);
}

pub struct InternodeServer {
    hostname: String,
    client_rx: Option<Receiver<Arc<Mutex<TcpStream>>>>,
}

impl Server for InternodeServer {
    fn new(port: u16) -> InternodeServer {
        let hostname = format!("127.0.0.1:{}", port);
        InternodeServer {
            hostname: hostname,
            client_rx: None,
        }
    }

    fn make_pipe(&mut self) -> Sender<Arc<Mutex<TcpStream>>> {
        let (server_tx, client_rx) = channel();
        self.client_rx = Some(client_rx);
        server_tx
    }

    fn hostname(&self) -> &String {
        &self.hostname
    }

    fn handle_client(&self, stream_arc: Arc<Mutex<TcpStream>>) {
        println!("Received connection");
        let mut stream = stream_arc.lock().unwrap();
        let response = b"Hello\r\n";
        let _ = stream.write_all(response);
        let _ = drop(stream);
    }

    fn run(&self) {
        loop {
            match self.client_rx.as_ref().unwrap().recv() {
                Ok(stream) => self.handle_client(stream),
                Err(RecvError) => break,
            }
        }
    }
}

fn main() {
    let mut s = <InternodeServer as Server>::new(10101);
    s.initialize(false);
    s.run();
}

Here's a smaller reproduction of the error:

use std::thread::Thread;

trait Server {
    fn initialize(&self) {
        Thread::spawn(move || self.handle_client());
    }

    fn handle_client(&self);
}

fn main() {}

The problem is that the argument passed to Thread::spawn must be Send . You are trying to move self into the closure, but your trait doesn't guarantee Send , so the closure can't be Send .

We can attempt to go down that path with trait Server: Send , but then we get "cannot infer an appropriate lifetime" errors because Send also requires 'static ( for now ). Also, it seems very strange to move yourself into a closure.

Really, I think you want to split up your code. Move handle_client into a separate trait and then ensure that implementations of that trait are Send :

use std::thread::Thread;

trait Server {
    fn initialize<D>(&self, driver: D)
        where D: Driver + Send
    {
        Thread::spawn(move || driver.handle_client());
    }
}

trait Driver {
    fn handle_client(&self);
}

fn main() {}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM