SE-0306: Actors

I don't think isolated struct or isolated enum makes sense because those types are passed by copy. Each actor having access to the value is going to have its own copy so there's no point in each copy having an execution context of its own. It's a bit like asking for indirect class: pointless.

But it does makes sense with global actors, as you mentioned, because the execution context is not part of the struct or enum.

Structured concurrency provides isolation as well, and both do so by building on SE-0302.

Actors are a declarative frontend to isolation, just as structured concurrency is the procedural interface to them. Calling one or the other the "isolation" system seems misleading and is technically incorrect.

-Chris

9 Likes

Indeed. But I was referring to the upcoming proposals about global actors and nonisolated which are also declarative frontends to isolation. Together actor + @MainActor + nonisolated are three very different ways to spell the same thing: which execution context can run a particular function. (And maybe @Sendable is kind of one too.) Can't we think of a way to make similar things more similar?

I am wondering if an actor / isolate should have methods after all or if it would be more like a container for objects and you would call methods on exposed objects of it instead. This way it is clear, that an actor is not a class but something different.

Would it make sense to look into a possible constraint spelling such as actor protocol Actor: ... {} in the future? Sure the typealias AnyObject = class is still a bit strange, but constraining protocols to type kinds would still be useful for library vendors. I think it would be additive to fix the declaration of the Actor protocol in such a way in the future.

An actor is not just a synchronization mechanism, it can have functionality of its own. Isolation is only one of the guarantees an actor gives you.

1 Like

The key distinction is: You can only exchange Sendable (SE-302) items with an actor. Actor can't expose mutable objects to the outside world, even asynchronously.

In order to protect the mutable state of an object graph with an actor, you need to create a full set of API on the actor to manipulate the object graph. Basically the actor becomes a façade for the object graph.

1 Like

First off, I am so excited about Swift getting first class support for actors with such a performant design and deep integration.

But I don't think the current proposal is there yet. I agree with Chris.

Keep this proposal exclusively focused on pure actor model.

Using class infrastructure to implement actors is an implementation detail. We can decide later to surface this capability (actor classes). For now, make actors implicitly final. Once you expose class capabilities of actors you can say actor declaration is short for final actor class and actor class would enable inheritance. The actor implemented as final class also removes the initializer complications as well.

I also agree with @Chris_Lattner3 that no synchronous access should be provided in the base proposal. Keep actors pure. Then we can tell the developers that if they use pure actors, they will get distributed processing and fault isolation almost for free. (Usual disclaimers apply: Depending on your system design and purpose)

After removing the above items from the proposal, I suggest improving examples to better showcase pure actor systems.

A side note:

From the feedback on the forums it is evident that the current Swift community is not very familiar with actor model of concurrency and its design patterns. The upcoming proposals that add features for ease of migration and integration with the existing ecosystem are going to muddy the waters even further and confuse people.

It is critical that we create high quality reference and training materials on actors and actor systems, focusing on new actor centric designs. Most of the focus right now seems to be on migrating existing code with minimal change and no redesign. This is not going to work very well and people will start to blame actors for it.

The proper use of actor model requires a redesign of many existing systems and needs correct understating of the actor model of concurrency. We should not overemphasize the mechanical details of actor implementation (as a class wrapping a logical serial execution queue). We should also properly introduce high level conceptual view of actor systems.

17 Likes

I haven't had time to fully write out my review but I wanted to respond to this point. My feedback will generally be the opposite of yours, especially these points. Having read, at a high level, about the actor model I've come to a simple conclusion: a pure actor model is not a good fit for Swift, for the all of the reasons you just listed. Purity for purity's sake is not valuable to Swift. As you say, actors are very similar to classes with an internal serial queue. At a high level this model is already possible in Swift (and Objective-C), just without the compiler verification. So why not take the pragmatic approach and expand the capabilities of things developers are already familiar with instead of forcing them to adopt a rigid model that doesn't fit in their existing code? To put it frankly, what does a pure actor model get Swift developers? I haven't heard a compelling answer to that question.

2 Likes

It is true that the actor model may not be a good fit for many parts of the existing uses of Swift as a client language for mobile app development within the existing frameworks.

On the other hand, having a performant compiler verified actor infrastructure with fault isolation and distributed actors will make Swift very relevant in entirely new domains. Just a historic example: Erlang actor model enabled the development of highly reliable and scalable "carrier grade" software that is still running a large part of the mobile phone networks around the world.

I am worried that people will start using actors incorrectly, get frustrated and bad-mouth Swift actors in general. This may turn off people from using actors when they are the correct fit.

I am very fond of Swift pragmatism and I am not an actor purist. But when the problem calls for actors, you need to be able to recognize it.

8 Likes

There needs to be differentiation between actors and distributed actors, as I understand them to be two related but different things that have a few key differences. I agree, under a distributed model, you must always be async. There's no other way the model works. But simpler, local actors are a different question, and I think this proposal is focused only on those. To my mind I think it would serve Swift well to provide an evolutionary path for classes that need more and more protection and distribution: class -> actor -> distributed actor. Given that path, distributed actors seem like a possible add-on for the future, not something we need right now.

Of course. Actor fault isolation (letting an actor crash and restart or be replaced or upgraded without restarting the application/system) and distributed actors are the next levels of the actor model and the main "meat" of it. If we properly teach some principles and design considerations about actor systems, the migration can move in the correct direction and with the correct level of expectation. This can prevent frustration of developers new to the concept.

4 Likes

Simple answers: Memory safety, and a “right by default” set of programming paradigms and an emerging set of design patterns.

Many of us are well aware of the capabilities and limitations of library-only actor models like Akka: the introduction of actors into swift doesn’t take away the ability to express those things using Swift 5 level language features. If you like them, keep (or start?) using them.

The introduction of first class actors allow us to go beyond that: to move the SoTA of language design forward, define away entire classes of bugs in practice, and enable an new future of multicore programming.

This does not make an old idea “possible”: its power is that it makes an old idea simple, accessible, and fun.

-Chris

28 Likes

I've read this proposal and the other concurrency proposals several times and am very excited. For async/await and Tasks I am all in and get it, as I've used Dispatch for like 10 years, however I can't fully grasp this Actor proposal. I haven't used this paradigm in any language before.

Since I do understand concurrency and use it regularly I can sort of see where this wants to go, but the proposal doesn't make it easy to understand what actors actually are. My mind imagines a class that has a DispatchQueue and always uses it to do anything, and I can tell this paradigm will end up being solved by actors; but I don't understand how yet.

@hooman mentioned revising the proposal with use cases and design patterns. I second that. The current proposal doesn't give me a good understanding of what Actors actually are. Reading through it I ended up speculating and coming up with my own conclusions.

I consistently didn't get the actor type name. At one point it was an actual type actor at other points it was actor class and as @michelf mentioned my first thought was why isn't it just isolated class? Is this a new type, and if so why? And if it's just a class with memory isolation then isolated class would go along with the access attributes private isolated class. But then I read all the debate about class inheritance and realized I don't have any clue what an actor is actually supposed to be.

So I'd like to see the next revision focus on creating better understanding of what Actors actually are possibly through real world use cases.

2 Likes

Compiler errors instead of hard to find bugs.

12 Likes

I understand, now, why I disagree so much with your stance.

This point made by @hooman made me realize why. As far as I understand, your reasoning comes from the idea that developers will have to upgrade their existing codebase to use actors. Is that really the case? I would love to get some clarification from Apple if we'll be obliged to use actors in our existing apps, assuming we want to be up to date with platform updates, of course.

If I understand @Chris_Lattner3 correctly here, he's saying that if you like using inheritance and don't want to go through the hassle of redesigning your app in terms of actors, you can simply continue to use the existing solutions.

If that's the case. I strongly disagree with the stance that, given a migration is not required, we should still design new features while focusing on legacy code.

If a migration is required, then I still prefer to think about the future instead of looking at the past, but at this point I see your POV with a bit more empathy. However, in general, I don't think favouring legacy code is a good strategy on the long run. Look at Microsoft's history with Windows and Apple's history with Carbon and Cocoa. This vision of looking to the future is the reason I prefer working with Apple instead of Microsoft. Sometimes we, developers, have to take the hit. It hurts, but that's what's best for everybody on the long run. I prefer that I carry the burden of migrating legacy code to a new technology so that other future developers don't have to keep carrying it later.

16 Likes

There is no migration required here. This is directly analogous to Swift 1.0 days which introduced prominent structs and enums into the language. Folks moving from Objective-C often brought patterns over directly and used classes in very familiar and idiomatic-to-objc ways, but structs and enums enabled new and better design patterns that could be adopted over time.

I see actors in the same way: classes/GCD/pthreads/etc are not going away. However, actors allow new design patterns and provide a lot of benefits over the old patterns. While I'm eager to get to the new world, I think that each developer will decide if and when it makes sense for their apps, and we'll live in a mixed world for a very long time.

-Chris

14 Likes

Why is a pure model required for that? Actors providing checked thread-safety is the whole point of this proposal. I merely object to the notion that we need a pure model to get it, since it's clear we don't, given this proposal. So again, what capability does the pure model give us?

No, I've never anyone "will have" to migrate to actors. But users will naturally want to transition any types they've created which manually ensure thread-safety to actors to pick up the compiler's guarantees and optimizations. Users creating new types with similar requirements will want to start using actors. And users importing existing types that need thread-safety will want to adopt it as well. Given all of these disparate requirements, it seems better to provide an evolutionary path rather than a clean cut, unless that cut gets us something. So far it doesn't.

This isn't just a focus on legacy code, it's a focus on all existing Swift developers.

I'm sorry, but this analogy just isn't applicable here. Swift is no longer a new language that has no existing codebases. And Swift's structs and enums offered obvious and powerful advantages over more compromised types. Even then there were still facilities for bridging some Obj-C struts and enums to their Swift equivalent, it wasn't a 100% break. So I really don't see an equivalence to this situation at all.

What new patterns? It seems like I could achieve everything actors bring by manually managing my thread-safety, it just isn't guaranteed by the compiler. Actors make such guarantees trivial, certainly, but don't seem to offer anything new like the value semantics of powerful types did when Swift was first introduced.

In the end, I still wonder what you all think a pure approach to actors would bring us that a hybrid approach doesn't?

1 Like

I feel guilty for using the word pure here. The model proposed here will not be describing a classic pure actor system, even after removing synchronous stuff.

This proposal adds an innovative addition to the classic pure actor model by making it very natural and easy to await the result (or completion of the processing) of the message.

Actually, I thought about proposing to add a send to be used instead of await when the message does not return a result and we don't want to await its completion. Having that send would make it more like classic pure actor model.

As Chris mentioned, you can achieve similar result with existing APIs but it would be more error-prone, harder to maintain and not as performant.

For example, copy-on-write arrays Ă  la Swift could be implemented and used well before Swift existed, but nobody bothered in practice.

You are underestimating how important it is to have such enabling technology being directly built into the language and fully supported by the compiler, optimizer and the standard library.

3 Likes

Two things First, it's difficult for me to underestimate the value of this feature given I've had to manually implement the equivalent functionality for Alamofire. Being able to replace that manual code with something automatic, compiler verified, and faster will be a great day indeed. Second, nothing I've written in this thread has even implied that the compiler verified model isn't the goal. I just don't see how the more pure model supported by you and others is any better at giving us that than the hybrid model proposed here, or a model with even more explicit support for synchronous access.

3 Likes