Can something be improved wrt use of thread-safe types?


#1

I’m still new to Rust, and while I’m starting to have a grasp about the data model, the borrow checker and other base things, I haven’t written multi-threaded code yet. Well, until now.

So, I wanted to use http://aatxe.github.io/irc/irc/client/server/struct.IrcServer.html from multiple threads. The type documentation is rather slim. Arguably, there’s an issue there. In retrospect, I could have found the examples in the crate repository and spared myself some trouble.

Anyways, “thread-safe” doesn’t tell much, so I tried various things with things with Arc, Mutex, etc.

They all failed to compile with variants of:

src/main.rs:19:5: 19:10 error: the trait bound `std::cell::Cell<u32>: std::marker::Sync` is not satisfied [E0277]
src/main.rs:19     spawn(move || {
                   ^~~~~
src/main.rs:19:5: 19:10 help: run `rustc --explain E0277` to see a detailed explanation
src/main.rs:19:5: 19:10 note: `std::cell::Cell<u32>` cannot be shared between threads safely
src/main.rs:19:5: 19:10 note: required because it appears within the type `irc::client::server::IrcServer`
src/main.rs:19:5: 19:10 note: required because of the requirements on the impl of `std::marker::Send` for `std::sync::Arc<irc::client::server::IrcServer>`
src/main.rs:19:5: 19:10 note: required because it appears within the type `[closure@src/main.rs:19:11: 21:6 server:std::sync::Arc<irc::client::server::IrcServer>]`
src/main.rs:19:5: 19:10 note: required by `std::thread::spawn`

… which is a pretty opaque failure from a user perspective.

As it turns out, I didn’t need to wrap the type at all, and just passing a IrcServer clone to the thread closure is how the type is supposed to be used in thread-safe manner.

Is there anything that could be done here to be more helpful?


#2

When auto traits are un-feature-gated, types will be able to explicitly mark themselves as !Send and/or !Sync, which will remove the repeated “required because” notes in the compiler output. So if IrcServer did this, the compiler output would say that IrcServer itself doesn’t implement Sync and cannot be shared between threads.

The “required because” messages are useful for users who are defining their own types, as it helps elucidate why a type wasn’t determined to be Send or Sync, but arguably they shouldn’t cross privacy boundaries. https://github.com/rust-lang/rfcs/issues/1744 suggests that this be controlled via a #[transparent] attribute, but rustc could potentially be changed to omit the notes on internals of things in other crates even without the attribute (I would think, anyway). This was the motivation for https://github.com/rust-lang/rust/pull/31982 and a preceding change that did the same thing for Rc.

Other than that, if you’re asking about a more general way for a type to indicate how it should be used with threads, it’s possible that an extension could be made to the #[rustc_on_unimplemented] attribute. Perhaps something like:

#[rustc_on_unsatisfied(Sync("move it into the thread closure instead, cloning if necessary")]
pub struct IrcServer { ... }

which could then (with the other improvements to the compiler) yield a message like

error: the trait bound `IrcServer: std::marker::Sync` is not satisfied [E0277]
note: `IrcServer` cannot be shared between threads safely; move it into the thread closure instead, cloning if necessary

This would have some limitations similar to #[derive], but could help with the common case of the thread-safety-related traits.


#3

A variant of this would be to have the attribute on the impl itself:

#[rustc_on_unsatisfied = "move it into the thread closure instead, cloning if necessary"]
impl !Sync for IrcServer {}

#4

From my limited experience with threading in rust, it looks like there are two patterns for thread-safe types: either they are Send’able and you have an instance that goes from thread to thread, or they are Clone’able, and you have a clone in each thread that, and the type implementation takes care of the details.

Send is pretty much explicit about what it means with multiple threads, but Clone actually doesn’t. So wouldn’t it make sense to have a separate trait, that would just mean the same thing as Clone as far as implementation goes, but that tells the developer and the compiler that the type is meant to be cloned in each thread (not quite unlike Eq being the same implementation as PartialEq, but extending its semantic meaning)