読者です 読者をやめる 読者になる 読者になる

Write and Run

it's a simple way, but the only way.

Rustのstd::io::copyとEchoサーバー

気が向いたので Rust でも書いてみるかという感じになって書いてるんですが、TCP Echo サーバーを書くだけで躓いたのでメモ。

fn main() {
    use std::io::prelude::*;
    use std::net::{TcpListener, TcpStream};
    use std::thread;

    let listener = TcpListener::bind("127.0.0.1:8124").unwrap();

    fn handle_client(mut stream: TcpStream) {
        std::io::copy(&mut stream.try_clone().unwrap(), &mut stream);
    }

    // accept connections and process them, spawning a new thread for each one
    for stream in listener.incoming() {
        match stream {
            Ok(stream) => {
                thread::spawn(move|| {
                    // connection succeeded
                    handle_client(stream);
                });
            }
            Err(e) => { /* connection failed */ }
        }
    }

    // close the socket server
    drop(listener);
}

handle_client 内、std::io::copy の引数に std::io::copy(&mut stream, &mut stream) とやりたくなるが、ownership が第1引数に渡ってしまうので第2引数が不正とみなされコンパイルエラーになる。それを避けるために try_clone() しているが、内部的にはファイルディスクリプタの複製をやっているようで、ちょっと無駄っぽい気がする。

std::io::copy を使わずにこうすれば平気。

    fn handle_client(mut stream: TcpStream) {
        let mut buf = [0; 128];
        let mut written = 0;
        loop {
            let len = match stream.read(&mut buf) {
                Ok(0) => break,
                Ok(len) => len,
                Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
                Err(e) => break,
            };
            stream.write_all(&buf[..len]);
            written += len as u64;
        }
    }

とはいえ、せっかくのユーティリティ関数使いたいよなぁ。

Rust に詳しい人、ベストプラクティスを教えてください。