Thanks for the reference to Actix; that's very helpful! I don't really know anything about the upcoming distributed actor; is there a place I can read about it?
In fact it's a common misconception that values don't have identity. We give them names all the time. The difference between value types and reference types w.r.t. identity is that if you do not have (exclusive) ownership of a value, you can't express its identity in absolute terms. We can express the identity of a value relative to some other value of which it is a part, that we might not own, by using key paths.
No, I meant what I said.
It makes no sense to be an actor for anonymous values - returned from functions, for example.
I disagree that it makes no sense for actors to be returned from functions, and can't understand why you'd make that assertion. They stop being anonymous the moment they are bound to a variable or a parameter, and if they are discarded, well, no harm done.
That really depends on two things:
- The implementation of actor copying: it needn't be a suspending operation if implemented in the second way I suggested here
- The context: As far as I can tell,
await itself has no meaning whatsoever inside an actor with value semantics, since the actor can't be re-entered. So it's not obvious to me that every async call needs an await even though it suspends.
This is simpler than it probably looks. Outside the actor, it is handled just as with any other value.
You're leaving out some context, since I don't see a call to modify here and I don't know which code “I” is…
Because the Document is a value, yes, the only code that can read it during a call to modify is code that is directly or indirectly called by modify, which exclusively owns the value while it is executing. That was always the case even with no asynchrony in the system. In fact, it seems to me that's effectively true even if Document were an actor with reference semantics, unless you don't care about the consistency of the various things you might read from it. Reentrant actors are not like ordinary reference types: they protect you from data races, but are much more prone to logical races, and there's no guarantee that consecutive reads without visibly intervening writes are observing the same state.
Note that with async, because modify returns Void, there's no need to suspend a caller that issues a call to modify.
Whether this arrangement is unwieldy is certainly a matter of opinion. Making a copy of Document is as simple as initializing a variable or appending it to an Array. All of Photoshop works this way; the undo history is just a series of document snapshots, and because it uses copy-on-write at multiple levels (just like most complex data structures in Swift), these copies are cheap. Any data that needs to be read while an editing operation is underway is read from something that isn't being edited.
With that said, I don't inherently oppose the idea: it just seems to me like a somewhat awkward fit. I certainly think it's worth exploring the design space, though.
Programming with value semantics definitely changes the way you approach problems, but the payoffs in terms of being able to understand what your code means, control its behavior, and even improve performance are huge. Most people never consider pushing value semantics to its limits, but the approach has proven itself in the applications with the strictest performance and user-experience demands.