Send for a pointer: Rust seems to "ignore" a wrapper

Today I've stumbled upon the curious behaviour of the Rust compiler. I want to send a pointer to another thread. Raw pointers aren't Send. A common way to circumvent that restriction is to define a wrapper type and implement Send for it. However, I think Rust decided it would no longer tolerate this chicanery, at least in some cases :slight_smile:

Here's the simple code snippet demonstrating the newfound strictness of the compiler (playground):

struct Wrapper(*mut ());

unsafe impl Send for Wrapper {}

extern "C" {
    fn foo() -> *mut ();
    fn bar(ptr: *mut ());
}

fn main() {
    let ptr = foo();
    // This closure compiles w/o any problems...
    std::thread::spawn({
        let ptr = Wrapper(ptr);
        move || {
            baz(ptr);
        }
    });
    // ...but in this one the compiler sees that I'm trying to trick it and
    // produces an error :)
    std::thread::spawn({
        let ptr = Wrapper(ptr);
        move || {
            let Wrapper(ptr) = ptr;
            bar(ptr);
        }
    });
}

fn baz(ptr: Wrapper) {
    let Wrapper(ptr) = ptr;
    bar(ptr);
}

Have I stumbled into a bug, or is there an explanation?

I think the crux is that, with let Wrapper(ptr) = ptr;, the closure is actually doing a "partial move"! You can replicate the error with let ptr = ptr.0;, and it become clear it's a partial move. i.e. You are actually moving *mut () instead of Wrapper.

And, apparently, this works:

    std::thread::spawn({
        let ptr = Wrapper(ptr);
        move || {
            // hint we are moving the whole wrapper...
            let ptr = ptr;
            let Wrapper(ptr) = ptr;
            bar(ptr);
        }
    });

Relevant doc

1 Like

Improvements in Rust 2021 got you.

In your 2nd example because you don't need your whole Wrapper type after first line compiler just takes β€œpart” of it, meaning the whole interior. And that one is not Send.

4 Likes

Thanks! Now it makes sense to me :slight_smile:

Instead of

let ptr = ptr;
let Wrapper(ptr) = ptr;

another option would be to use that block expressions always move their return value and write

let Wrapper(ptr) = { ptr };
4 Likes