This is my coding along with this wonderful book. In general, I'll try to implement a chapter, update that implementation with the book's implementation, and record my learnings in the README.
Unfortunately, I read this chapter before attempting an implementation. Luckily,
I was still given the opportunity to learn a lot about Send/Sync! Most of
the details are summarized in this issue but I'll summarize
here.
I originally thought that Send was for transferring ownership of a value
to another thread. So I thought this was specifically relating to sending
a T. However:
- I didn't notice the fact that, if you send
&mut T, the receiving thread can usestd::mem::swap(or similar) to get an ownedT. SoSendis definitely required for sending&mut T. - Since
Sendwas forTand I hadn't really thought about&mut T, I assumed that you neededSyncto send non-T. That's untrue -Syncis only required if you need concurrent access to&Tfrom multiple threads. The reason why&T: SendimpliesSyncis because&Tis copy - if you're allowed to send&Tto one thread then you're allowed to make copies and send to many threads. However, if you're in a situation where you have exclusive access (e.g.Mutex, spin lock), then you don't needSync. - You need
Sendeven if you only access&T. If, for example, we remove theDerefMutimplementation on ourSpinLockGuard, we'd still need to requireT: Sendtoimpl Sync for SpinLock. When we talk aboutSendand "sending values to another thread" that doesn't mean passing ownership - that means doing anything withT,&T, or&mut Ton a thread that the originalTwasn't initialized on. Some more details in this wonderful StackOverflow answer.
As with Chapter 4, I'd unfortunately already skimmed this chapter before attempting an implementation. Luckily, I did such a bad job at reading that I got plenty of stuff wrong which leaves room for learning!
- Preventing sending the
Receiveris a much easier way to avoid deadlocks (though I'll need to investigatestd/crossbeamto see how this is handled in the wild). - By dropping unsent messages in the
Dropimplementation ofInnerwe dodge some racy logic involving an extrawaitingvariable and avoid some kludgyArc::strong_counting. - Implementing
SynconInnerand then letting autoimplementations bubble up to the public types is better. This is also useful in case you makeInnerpublic later (as we would if we were avoiding the inner allocation). AtomicBool::get_mut()is a neat API for when you have&mut self.