Marking a struct as Send if it is boxed

Hey guys,

I am trying to create a wrapper around libusb asynchronous transfers. As part of that, I have a struct that stores pointers allocated from FFI. Knowing that these pointers will only ever be accessed from the containing struct, I figured it should be okay to mark it as Send.

struct Foo {
    transfers: Vec<*mut libusb_transfer>
}

However, libusb_transfers contain a pointer back to the containing struct.

fn bar(&mut self, ...) {
...
unsafe {
    (*transfer).user_data = self as *mut Self as *mut c_void;
}
...
}

So when self is on the stack and moved to another thread, it gets a new address which means the pointers are wrong. Is it okay to assume, that placing Foo in a Box should ensure its address stays the same, even when moved to another thread? If so, how can I mark Foo as Send only if it is boxed?

Best,
Flo

You seem to be confused about what Send means. It has nothing to do with self-referentiality. Send means "this type is safe to send by-value to another thread". Whether it is on the stack or on the heap cannot possibly change this property. Box<T>: Send exactly if T: Send.

3 Likes

I think I got that; my issue is that my struct is not transitively Send because it contains raw pointers. However I would like to unsafe impl Send for Foo {} as I believe it should be sound to move these pointers to another thread (they are heap allocated across FFI and completely managed in Foo).

But due to the self-referentiality, this is only okay if the address (which is stored in these pointers) does not change. Which is why I'd want to do something like unsafe impl Send for Box<Foo> {}, which of course does not work.

This is true if self is moved in general, not only to another thread. Sendness isn't about moving things around in memory, it's specifically about thread safety. You don't have a thread safety problem, you have a moving-things-around-in-memory problem, so Send is the wrong tree.

2 Likes

You enforce this with pinning, not by mucking around with Send.

After all, even if your value is not Send, I can still move it. I just can't move it to other threads.

4 Likes

Similarly, even if I wrap it in a box, it's still not okay. You can use std::mem::swap to swap the contents of two boxes.

2 Likes

The misconception here is that as long as self is kept in the same thread it won't change address. This is false. It can and will happen very easily, for example just by returning it from a fn new() -> Foo function or assigning it to a different variable.

5 Likes

Thanks for the input, that makes sense.

So is what I am trying to do simply not possible?

Preventing addresses from changing can be done. It's what pinning is for.

1 Like

I'm not sure what is it you are actually trying to do, because you are asking for a thing that doesn't make any sense.

If you are trying to build a self-referential type, then:

  1. Don't.
  2. Or if you really have to, then heap-allocating (e.g. Box) is in itself sufficient, regardless of threads. Whether or not you have threads, moving a value around can change its address, and whether or not you have threads, a heap-allocated value will not change its address.
  3. You could also use reference counting (Rc or Arc and Weak) for making one value forcibly keep another one alive. This is probably the safest option if you bother building tiny, compartmental abstractions for every part of your system.

Beside that, it's really unclear what you are trying to do with threads, or what any of this has to do with threading.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.