4

Still struggling with the Rust mind shift, now I have this use case - one configuration for a multi-threaded TcpListener:

use std::net::{TcpListener, TcpStream, ToSocketAddrs};
use std::thread;

fn main() {
    serve("127.0.0.1:3333", Configuration { do_something: true });
}

//#[derive(Copy, Clone)]
pub struct Configuration {
    pub do_something: bool,
}

pub fn serve<A: ToSocketAddrs>(addr: A, conf: Configuration) {
    let listener = TcpListener::bind(addr).expect("bind failed");

    for stream in listener.incoming() {
        match stream {
            Ok(stream) => {
                thread::spawn(move || {
                    handle_client(stream, &conf);
                });
            }
            Err(e) => {
                println!("Connection failed: {}", e);
            }
        }
    }
}

fn handle_client(stream: TcpStream, conf: &Configuration) {
    if conf.do_something {
        //stream....
    }
}

I'm happy that the TcpStream is consumed by handle_client, that's it's purpose, but why does Configuration have to be copied for each thread? I'd like to have just one copy and share an immutable reference with all threads. Is that possible? Or perhaps I'm missing the point.

Why do I need the Copy and Clone traits if I'm passing Configuration by reference? This confused me a good deal:

error[E0382]: capture of moved value: `conf`
  --> src/main.rs:19:64
   |
19 |                 thread::spawn(move || { handle_client(stream, &conf); });
   |                               -------                          ^^^^ value captured here after move
   |                               |
   |                               value moved (into closure) here
   |
   = note: move occurs because `conf` has type `Configuration`, which does not implement the `Copy` trait
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Robert Cutajar
  • 3,181
  • 1
  • 30
  • 42

1 Answers1

6

Implementing Copy (Clone is incidental here) only fixes the problem because implementing it allows the compiler to implicitly duplicate the Configuration struct, passing a copied value into the thread.

It needs to pass the variable by value because you told the compiler to move all used values into the closure:

thread::spawn(move || {
//            ^^^^ HERE
    handle_client(stream, &conf);
});

The entire purpose of the move keyword is to tell the compiler "no, don't try to infer how variables are used inside this closure, just move everything in".

When you move &conf, the compiler says "OK, I'll move conf into the closure then take a reference to it".

In your case, you can just remove the move keyword:

thread::spawn(|| {
    handle_client(stream, &conf);
});

If you really need to be able to use the move keyword and pass in a reference, you need to move in a reference:

let conf = &conf;
thread::spawn(move || {
    handle_client(stream, conf);
});

This still doesn't allow your code to compile because there's no guarantee that the reference outlives the thread. That's discussed thoroughly in Passing a reference to a stack variable to a scoped thread.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Thank you @Shepmaster, I gather that I cannot share a reference to conf with the thread because the thread may outlive the scope so again I would need a scoped thread for that. Correct? If I just remove the `move` I get the "may outlive borrowed value `conf`". – Robert Cutajar May 26 '17 at 06:31
  • @RobertCutajar-Robajz yes; that would be why the final paragraph of my answer says *This still doesn't allow your code to compile because there's no guarantee that the reference outlives the thread* and then links to an answer that talks about scoped threads. Is there some other way I could have worded that to be more understandable? – Shepmaster May 26 '17 at 13:35
  • Aha! :o) I thought you refered to the "move in a reference". Perhaps it would be clearer to say upfront that I cannot do this immutable reference sharing with thread::spawn at all without a copy. Thanks a lot for your effort to educate the seeker :o) – Robert Cutajar May 26 '17 at 15:12