But ... semaphores and queues are indeed very similar, and everything you said about a queue is also true for a semaphore. A queue has two fundamental operations:
trait Queue<T> {
fn push(t: T)
fn pop() -> T // waits until the queue isn't empty, then does the pop()
}
A semaphore is is just a Queue where T is a constant (typically the unit type, which has zero size). Since it's always the the same thing there is no need to store the actual items as pop() can just create copies as needed, which also means push() can just discard it's argument. That means you can get away with just storing a count of how many items are in the queue, not the items themselves. Now rename:
push --> signal
pop --> wait
And bingo, we have a semaphore. Which also means if you are having difficulty reasoning about semaphores but find queues easy to think about, just reverse the name transformation above and semaphores become just as easy.
Rust exploits a similar equivalence with a mapping and a set: a set is just a mapping that maps every key to the unit type.
The other half of it is, thread blocking on a single queue. And system message queues being a debugger friendly concept (not just a declared local). Code discipline is 2/3 of the solution.
Rust exploits a similar equivalence with a mapping and a set: a set is just a mapping that maps every key to the unit type.