I'm trying to develop a simple Rust chat server. I'm not a Rust expert and come from Java and Kotlin.
This same server in Kotlin is:
import java.io.* import java.net.* import kotlin.concurrent.thread import java.util.concurrent.ConcurrentHashMap fun main(args: Array<String>) { val serv = ServerSocket(Integer.parseInt(args[0])) //val users = ConcurrentHashMap<String, PrintWriter>() val users = Collections.synchronizedMap(HashMap<String, PrintWriter>()) while (true) { val s = serv.accept() thread { var sin = s.getInputStream().bufferedReader() val sout = PrintWriter(s.getOutputStream(), true) // read nick val nick = sin.readLine() users.put(nick, sout) sin.forEachLine { for (peer in users.values) { if (peer == sout) continue peer.println(it) } } users.remove(nick) } } }
After many attempts I've come up with a working Rust implementation :
use std::env;
use std::io;
use std::io::Write;
use std::io::{LineWriter, BufReader, BufRead};
use std::net::{TcpListener, TcpStream};
use std::sync::{Arc, Mutex};
use std::collections::HashMap;
use std::thread;
use std::ops::DerefMut;
fn main() -> io::Result<()> {
let args: Vec<String> = env::args().collect();
let server_socket = TcpListener::bind(&*format!("localhost:{}", args[1]))?;
let users: Arc<Mutex<HashMap<String, LineWriter<TcpStream>>>> = Arc::new(Mutex::new(HashMap::new()));
for socket in server_socket.incoming() {
let users = users.clone();
thread::spawn(move || -> io::Result<()> {
let socket = socket?;
let socket_copy = socket.try_clone()?;
let mut inp = BufReader::new(socket);
let out = LineWriter::new(socket_copy);
// read nick
let mut nick = String::new();
inp.read_line(&mut nick)?;
nick.pop(); // discard '\n'
let nick_copy = nick.clone();
{
let mut users = users.lock().unwrap();
users.insert(nick, out);
}
for line in inp.lines() {
{
let line = line?;
let mut users = users.lock().unwrap();
for (nick, peer) in users.deref_mut() {
if *nick == nick_copy { continue; }
writeln!(peer, "{}", line)?;
}
}
}
// remove nick
{
let mut users = users.lock().unwrap();
users.remove(&nick_copy);
}
Ok(())
});
}
Ok(())
}
Too verbose for my taste.
I'm particularly dissatisfied with those clone ops. When to move? When to borrow?
I'd prefer to use for peer in users.values_mut()
instead of for (nick, peer) in users.deref_mut()
but then I've problems when comparing LineWriter
references to discard sender.
I'd also prefer to use RwLock
instead of Mutex
.
Any pointers to simplify/enhance code will be greatly appreciated.