如果我正在与多个线程共享不可变引用,为什么需要实现`Copy`和`Clone`?

4

你可能还在为Rust的思维转换而苦苦挣扎,现在我有这样一个使用场景 - 一个多线程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....
    }
}

我很高兴看到TcpStream已被handle_client使用,这正是它的用途,但为什么每个线程都要复制Configuration呢?我希望只有一份拷贝,并与所有线程共享不可变引用。是否可能?或者我可能没有理解重点。
如果我通过引用传递Configuration,那么为什么还需要CopyClone特性呢?这让我感到困惑。
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
1个回答

6

实现Copy(这里无关Clone)只是解决问题的一种方式,因为实现它允许编译器隐式复制Configuration结构体,并将复制的值传递到线程中。

它需要按值传递变量,因为您告诉编译器将所有使用的值move到闭包中:

thread::spawn(move || {
//            ^^^^ HERE
    handle_client(stream, &conf);
});
< p > move 关键字的 整个目的 就是告诉编译器“不,不要尝试推断闭包内部变量的使用方式,只需将所有内容移入其中”。

当你移动 &conf 时,编译器会说:“好的,我将把 conf 移入闭包中,然后引用它”。

在您的情况下,您可以删除 move 关键字:

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

如果你确实需要能够使用 move 关键字并传递引用,那么你需要在引用中进行移动操作。
let conf = &conf;
thread::spawn(move || {
    handle_client(stream, conf);
});

这仍然不能保证您的代码编译成功,因为没有保证引用对象能够维持在线程完成之前。这个问题在将引用传递给作用域线程中得到了充分讨论。


谢谢@Shepmaster,我了解到我不能与线程共享对conf的引用,因为线程可能会超出范围,所以我需要一个有作用域的线程。是这样吗?如果我只是删除“move”,我会得到“may outlive borrowed value 'conf'”错误提示。 - Robert Cutajar
@RobertCutajar-Robajz 是的,这就是为什么我回答的最后一段说“这仍然不能让你的代码编译,因为没有保证引用会比线程更长寿”,然后链接到一个讨论作用域线程的答案。有没有其他方式可以表达得更易懂? - Shepmaster
啊哈!:o) 我以为你指的是“在引用中移动”。也许事先说清楚,我无法在没有复制的情况下使用thread::spawn进行不可变引用共享。非常感谢您努力教育寻求者 :o) - Robert Cutajar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接