尽管闭包仅捕获 Send 变量,但它并不是 Send

rid*_*ish 5 rust

我有以下 Rust 代码(rustc 1.75.0),它“强制”发送一个类型:

struct ForceSend<T>(T);
unsafe impl<T> Send for ForceSend<T> {}

pub fn perform<T>(t: T) {
    let wrapped = ForceSend(t);
    takes_send(move || {
        wrapped.0;
    });
}

fn takes_send<F: Send>(_: F) {}
Run Code Online (Sandbox Code Playgroud)

我希望这段代码能够编译,因为闭包通过移动捕获单个值wrapped,并且该值是 Send。根据文档

如果非唯一不可变引用捕获的所有变量都是同步的,并且唯一不可变或可变引用、复制或移动捕获的所有值都是发送,则闭包为发送。

然而,这无法编译,报告关闭不是发送

为什么这不能编译?谢谢你的帮助!

ζ--*_*ζ-- 6

wrapped.0.0仅移动中的字段wrapped,该字段的类型为 T(非发送)。这通常对于部分移动很有用,您可以移动捕获字段的子集,并将其余字段保持为未移动。

在这种情况下,您必须确保移动整个 对象,例如通过创建将整个对象移动到闭包中的wrapped绑定:Wrapped

struct ForceSend<T>(T);
unsafe impl<T> Send for ForceSend<T> {}

pub fn perform<T>(t: T) {
    let wrapped = ForceSend(t);
    takes_send(move || {
        let w = wrapped;
        w.0;
    });
}

fn takes_send<F: Send>(_: F) {}
Run Code Online (Sandbox Code Playgroud)

[操场]

与此无关的一点是,如果T确实不受限制,则此代码可能不健全,并且如果takes_send确实想要跨线程发送内容,则可能会导致 UB。例如,如果从与获取线程不同的线程中释放互斥体/相关的某些操作系统实现,则会发生故障,并且MutexGuard跨线程发送可能会产生这种情况。


顺便说一句,这是一个在更有用的情况下捕获期间部分移动行为的示例:

pub struct Foo(String, String);

pub fn perform(t: Foo) {
    takes_closure(move || {
        t.0
    });
    
    takes_closure(move || {
        t.1
    });
}

fn takes_closure<F: FnOnce() -> String>(_: F) {}
Run Code Online (Sandbox Code Playgroud)

[操场]