I agree, we haven't found a general solution to building concurrent programs without the downsides that most of the existing models incur. The Actor model doesn't remove the burden to handle edge cases from the programmer, and depending on the problem domain it might be the wrong choice, but it does solve certain kinds of problems really well.
It's about using the right tool for the job. For example, I find that Futures / Promises great for composing calls to consume various backend services. But when it comes down to concurrent processes coordinating work and being able to handle failure transparently, Actors are my first choice most of the time. And sometimes the pragmatic solution is to simply use Threads and Locks.
I'm just curious about what you mean with the following statement:
> And yet, two messages can absolutely race.
The queuing and dequeuing of messages in an Actor's mailbox are atomic operations, so there cannot be a race condition within an Actor's state. An actor handles messages in sequential fashion. But yes there could be surprises by certain patterns on the incoming messages if that's what you meant.
Consider the following example:
Imagine three actors: a Bank Account actor and two ATM actors. One of the ATM actors sends the message 'GetBalance' to the Bank Account actor, which in turn replies with a 'CurrentBalance' message. Now that the ATM knows that there are enough funds it sends a message to set the balance to a different value, but in the mean time the other ATM had already set the balance to something else. This of course is a problem since the getting and setting the balance are not atomic (two independent messages).
But this could be solved by removing the 'SetBalance' message and instead having a 'Withdraw' message which the Bank Account actor can reject and reply with the proper 'NotEnoughFunds' message.
Basically:
ATM-1 sends GetBalance to BankAccount
BankAccount replies Balance(500 EUR) to ATM-1
ATM-2 sends Withdraw(400 EUR) to BankAccount
ATM-1 sends Withdraw(200 EUR) to BankAccount
BankAccount replies NotEnoughFunds to ATM-1
ATM-1 sends GetBalance to BankAccount
BankAccount replies Balance(100 EUR) to ATM-1
ATM-1 would now display the problem to the user and show the new balance (and he should worry since somebody withdrew his money from another ATM).
In the Actor model, each actor is expected to manage its own state, not "leaking" the control to the outside world. Allowing a 'SetBalance' message is giving control over its state to the outside world, and instead the actor should expose behaviour while retaining the possibility to decide what to do given the intention behind the message.
The nice part about this model of concurrency, is that it follows quite closely how real life processes work, so it can become quite intuitive to come up with and understand many of the patterns used.