What is the practical case for `send_wrapper`?

So I was pairing with a coworker today to try and debug a deadlock in some Pyo3 code, and because of that I was reading on pyo3::marker::Python::allow_threads, and because of THAT I started reading the module documentation, and THAT informed me of the existence of the (send_wrapper)[https://docs.rs/send_wrapper/latest/send_wrapper/index.html] crate, which of course I had to read about.

The only thing is...I couldn't figure out what you would use it for. The example in the send_wrapper documentation shows how you can move the send wrapper to a new thread, and then send it back to the original thread, but it doesn't really explain why you would want to do that. Why would you need to have ownership of an object in a thread that you can't examine/manipulate? Why not leave it in the original thread?

I'm not trying to be dismissive, I'm just not particularly strong in multi-threaded code so I'm having trouble conceptualizing it. I tried to read some Gtk code examples but it didn't really click there. I don't have a real need to use this crate, I'm just trying to understand when I would.

Myself, I've only used send_wrapper in cases where a value won't be used on another thread, but I can't tell the type system that (the value is being put in a place where all values must be Send). send_wrapper is not optimal for that use case, but it's guaranteed to be sound, compared to the strategy of just an unsafe impl Send wrapper that doesn't check the current thread like send_wrapper does.

As to why you would actually send a value to another thread even though you can't use it — well, I've never had such a case, but I can imagine one easily enough, especially since you brought up GTK. Suppose your application processes a lot of objects in the background — let's say, large collections of text documents it is OCRing — and each one has a Pixbuf that's a thumbnail for the document, which is displayed in the user interface to represent which stage of processing it's in. Then the job data structure (which is being stored in some background execution queue) might contain something like a SendWrapper<Pixbuf> which the job scheduler can send back to the UI thread when it wants to add a specific thumbnail to a list/gallery on screen indicating that some document is in progress or done. The advantage of this over ensuring the Pixbuf stays on the UI thread is that, if you tried to do that, you'd have to essentially make a whole object-RPC mechanism — assigning a unique ID to each such value, and sending commands to operate on them indirectly, including deleting them from a table of values when no longer needed.

Now, if your question is “but why would the UI library have so many things that are restricted to a single thread?” — well, one reason is that UI libraries are typically very complex C or C++ code, and faced with the trickiness of getting threading right without the assistance Rust offers, many UI libraries have decided to not even try. Another factor is that UI generally operates largely in synchrony with the event loop that actually processes user input, and requiring all programmatic actions upon the UI to happen in the event loop thread means that the system can simply not need to answer questions along the lines of “what if a button is deleted while it's being clicked?”, because mutual exclusion is implied by the fact that the event loop processes only one event at a time (and “request from another thread” is often considered a kind of event).

Implementing UI is complex, and anything that makes it simpler to implement, for the same user-visible result, is worth considering.

1 Like

So in this case you're using an API that requires a Send bound, but won't actually send the parameter to another thread, so you're just wrapping it to satisfy the bound?

That seems like a weird API bound but I can't say I understand multithreading well enough to say it doesn't make sense.

Yeah, after I posted this thought occurred to me, of a way you could use this--essentially the SendWrapper takes the place of an "id" that you send back. I'm not 100% convinced that this would be the best approach; from what I read of UIs, it seems likely that you're making that object-RPC system anyway (that's essentially what ECS does, right?). And it seems like you're just sending more data with the SendWrapper than just an ID (although I don't know if that's a significant overhead, and maybe sending SendWrapper<Rc<_>> would mitigate that). Plus you have the problem with not dropping the SendWrapper on the second thread, so if something goes wrong you still have to send it back. But on the other hand, if you maintain a lookup table then maybe you are less precise about when things can get cleaned up and you use more memory, so maybe you benefit that way. I haven't really done UI programming so this is all speculation by me.

Thanks for responding!

Imagine some library providing a type for you to own, that stores some sort of Box<dyn TraitYouImplement + Send>. Then your type that implements TraitYouImplement has to be Send even though it's up to you to decide whether to actually move the container. Ideally this restriction to a concrete trait object type doesn't happen, so the user of the type can decide whether or not a Send bound is wanted, but sometimes that is infeasible because it would require adding way too many generics across all the types and functions.

1 Like

This is as practical a reason as any. Thanks for responding!