SE-0306 (Second Review): Actors

Might as well throw in some reflections here, seeing as I'm co-authoring but not part of the core discussions so didn't have much chance to share thoughts other than directly with some members of the team:

Reentrancy

This remains quite nerve nerve-racking, and perhaps somewhat under the radar, but given the tradeoffs we have to take, I think we're doing the best we can here – so I'm okey with the current proposal where we move this off into future work.

To answer some questions that came up about it:

Sadly that's not really something tooling-detectable -- any kind of reentrancy is potentially dangerous. For now we'll have to live with "be careful, notice that there's an await somewhere".

The tricky thing with these things is that sometimes it is actually exactly what you want -- e.g. some helloCount being incremented by interleaving hello() calls is exactly what you'd want. But in other cases while executing transferMoney() some interleaving call changing destination is terrifying. So sadly there's no good tooling as it all depends on what the "meaning" of variables are.

And rightfully so! It is very hard to program a fully consistent actor in face of re-entrancy which server developers know all to well... But I recently also had UI developers realize and become worried about it. There are workarounds to "pretend" non-reentrancy, and perhaps I should write them up soon as it might be helpful to others facing this issue. The patterns boil down down to "per request/work actor" spawning, such that they may not be re-entered by anyone since they only have one entry point kicking off the work. So in that sense, we have workarounds until we're able to re-visit the topic and solidify the best practice into a language feature -- this sounds like the right order to me.

Summary: :+1: I remain very nervous about introducing an actor type that does not really protect from race conditions; unlike any other actor runtime out there (no, this is not a case of "everyone is doing it wrong"), but I agree we can address this with future work and fine-grained reentrancy control. It would have been best to solve this in one go, but time wise that's sadly not realistic.

Implicitly nonisolated Sendable lets

It's very true that is is a pretty weird concept and one that was quite surprising to me as well as we developed the design. Though this isn't one of the topics that had me worried about the design, and I mostly thought "oh well, seems it'll make the app developers happy".

I will say though that connecting this with distributed actors is a bit far fetched, as IMHO we should not be allowing distributed properties to begin with anwyay, even even if we did, it would not be implicit but require opting in (same as a distributed func needs the contextual keyword to be distributed). But I digress :slight_smile:

Summary: :+1: This implicit feature has not really shown up in practice as much as one might have thought while converting applications and it indeed is very weird in the actor sense of the world. I'm happy that we're not pushing for it as it indeed makes one less edge case in the design :+1:

I am more than happy to spell out nonisolated let when necessary and think that also is clearer than inferring it based on the type.


I'd like to address a small side note if this affects the distributed actor design or not really that has popped up:

Yeah, that's all correct.

I don't think the implicit-non-isolated-let ever was an option anyway for distributed actors at all, because there's by far more limitations imposed by such type (e.g. serializability of exposed types, necessarily throwing on operations because network boundaries etc.).

And, last but not least, since I'd also be strongly against allowing distributed var anyway, meaning that only distributed func are IMHO allowable to be accessed on distributed actors -- this is in order to encourage proper programming style with such types; It really isn't the right way to think about distribution as "oh, just set a field remotely"


actor inheritance

Way back when we discussed this at first in the initial pitch threads I was arguing that we should support this just because it does not "cost much" and indeed there are a few use-cases I had in mind.

In this review iteration I'd like to revise my previous remarks on this -- as I've just spent months of battling initializer semantics :wink: Indeed, I had not fully internalized how painful they are in Swift, with the various rules governing class initialization. It indeed is quite painful and perhaps we can do a better job if the feature becomes necessary.

I do have a number of "would be nice to do only as a library, without compiler work" designs in mind, e.g. PersistentActor style APIs, that I would like to explore in the future, and lack of inheritance makes them infeasible or pretty ugly.

But on the other hand, perhaps this is yet another reason to do-the-right-thing™ and double down on compile-time-metaprograming (see also Serialization in Swift) rather than abusing inheritance. That would be the best outcome for all kinds of reasons.

Summary: :+1: on banning actor inheritance, and if we ever need it, let's revisit and perhaps introduce some simplified model of it.


All in all, this is shaping up excellent and I'm very pleased with phrasing everything in terms of "actor-isolated" and Sendable types and closures -- it really clicks quite well, even if we'll need another year to handle all edge cases.

10 Likes