SE-0306: Actors

Those additions are essential in the initial success of the model and will remain relevant and extremely useful in the long run. I am not asking to drop them at all.

All I am asking is making sure people understand the actor model correctly. Reducing the initial clutter can help in that regard. An actor-centric concurrent system has the potential to have fewer bugs and more reliability, resilience, flexibility, maintainability and scalability.

The actor additions proposed (here and in the upcoming proposals) give us shortcuts and workarounds that make many things easier to achieve as long as we are focused on local actors and integration with the existing legacy code.

The problem is, those additions achieve that ease and integration to some degree at the expense of the positive attributes I mentioned above for an actor-centric design.

2 Likes

Can actor conform to protocol Hashable?

actor A: Hashable {
    var i: Int
    
    init(i: Int) {
        self.i = i
    }
    
    static func == (lhs: A, rhs: A) -> Bool {
        lhs === rhs
    }
    
    func hash(into hasher: inout Hasher) { // Actor-isolated instance method 'hash(into:)' cannot be used to satisfy a protocol requirement
        hasher.combine(ObjectIdentifier(self))
    }
}

Is this correct?

actor A: Hashable {
    var i: Int
    
    init(i: Int) {
        self.i = i
    }
    
    static func == (lhs: A, rhs: A) -> Bool {
        lhs === rhs
    }
    
    nonisolated func hash(into hasher: inout Hasher) {
        hasher.combine(ObjectIdentifier(self))
    }

    // must have hashValue
    nonisolated var hashValue: Int {
        var hasher = Hasher()
        hash(into: &hasher)
        return hasher.finalize()
    }
}
1 Like

I tend to agree with the conservatism that actors shouldn’t allow inheritance from day 1.

However! — if they mostly do behave like classes, for clarity when talking about them it would be helpful to be able to differentiate between the definition of an actor and an instance of an actor.

We have that separation today with classes and objects. The class is a definition, and the object is an instance.

It would be very helpful to have a similar distinction for actors. Perhaps an ‘actor class’ and an ‘actor object’? And ‘actor objects’ could be shortened in speech to ‘actors’.

For example with global actors, ‘the main actor’ would clearly refer to the main actor object (and not the main actor class).

And so I’d propose putting this clarity in the syntax (esp if actor classes do support inheritance):

actor class BankAccount {
  let accountNumber: Int
  var balance: Double

  init(accountNumber: Int, initialDeposit: Double) {
    self.accountNumber = accountNumber
    self.balance = initialDeposit
  }
}

let janeAccount = BankAccount(accountNumber: 123, initialDeposit: 400)
let eveAccount = BankAccount(accountNumber: 789, initialDeposit: 2000)

// BankAccount is an actor class
// janeAccount is an actor object (or actor for short)
// eveAccount is an actor object (or actor for short)

I’d believe this would make talking and writing about actors much easier. Otherwise I imagine upon hearing the term ‘actor’ it will prove hard to tell which one is being talked about.

1 Like
  • It gets a thumbs up from me :+1:, with reservations
  • Over my career (in C/C++), I have ended up reinventing actors (or wishing to be able to reimplement some objects/classes in what would have ended up being an actor) enough times that the need in Swift is an open and shut case for me.
  • Definitely, even if I do think some alternatives would fit just as well.
  • Whenever I used actors I had to reinvent them, so a language-integrated actor wins this question by default.
  • I gave it a read from top to bottom, making sure the initially unclear parts didn't remain unclear (e.g. I had some trouble with sentence "The restrictions on cross-actor references only work so long as we can ensure that the code that might execute concurrently with actor-isolated code is considered to be non-isolated" until I read it as "…considered to have renounced its actor-isolated property"), then slept on it.

Review

To the extend that I understand Swift in general and this proposal, it fits well in Swift. For instance, traditional isolation systems always have this ambiguity about whether you can call an isolation entry point while already isolated, leading to such tragedies as the generalisation of recursive mutexes. Here the proposal can infer that including for closures, similarly to type inference, and I have no reason to believe the inference to be problematic.

Syntactically, not much to dispute. In particular, actor are sui generis enough that the previously considered "actor class" syntax feels misleading to me.

However, I would appreciate more discussion as to how this proposal, either alone or with existing or proposed Swift features, can support common scenarios. That would in turn foster discussion as to whether some of these interactions need to be made first-class language features, or not, before the community ends up having to build their own patterns.

I have two examples. The first one would be an actor which implements an animation engine like Core Animation. In such a case, the client cares about starting the animation, but generally needn't stick around until it's done, i.e. the task within the actor completes. Fortunately, there is a solution for that: used a detached task from https://github.com/apple/swift-evolution/blob/main/proposals/0304-structured-concurrency.md and from that invoke the actor, and that can be hidden behind an API, but maybe it needs better, more structured language support.

The second one is directly related to reentrancy. It is a very structuring design decisions, whose ramifications are manifold, and common ways to deal with the fallout might be worth investigating as language features. And my second example is precisely the one that is used to discount non-reentrant design: the asynchronous cache. The redundant work the proposed implementation implies when a URL is requested while the same URL is already in the process of downloading may well be unacceptable for many applications (even if the download system has a cache of its own -- and remember not every URL is HTTP(S) and/or cacheable -- redundant decoding remains). The only way I see is through the use of, yes, again, a detached Task: the cache would factor the "slow path" as a detached Task that returns the image, and would store the handle of that task in an additional dictionary (before itself awaiting on the detached task); a hit in that dictionary would cause the cache code to await the return of that detached Task (it is possible to await a detached Task from multiple locations, right?). That is still somewhat structured, but feels more awkward than it could be, even if I don't have any particular proposal here.

(The same way, I remain unconvinced by the deadlock-avoidance argument: in most cases you end up replacing deadlocks with livelocks when the two objects end up depending on one another, so what has been gained is unclear to me)

Finally, I think actors will need some object model, but that can happen eventually: a first version without it would be viable.

1 Like

I personally feel the strong instinct that actors should not be modeled as specialized classes i.e., should not have any inheritance. I use the word "instinct" to admit that my concrete historical and technical knowledge in this domain is low. I'll explain my reasoning/current understanding in a series of bullet points, hoping to be corrected on any and all flaws:

(I've bolded the main points to make it easier to skim)

  1. class in Swift can essentially be thought of as a two-pronged feature: reference semantics and class-style inheritance, bundled together as one.

  2. This has always felt to me like a violation of fundamental architectural principles (no offense intended - I'm a Swift enthusiast and evangelist).

  3. final class tames the beast more or less down to a single feature, but not totally because we're still left with much of the burden imposed by inheritance (e.g., initializer rules).

  4. final class is therefore somewhat like having a folding travel fork as part of your permanent home cutlery set - you never make use of the folding mechanism, but you still have to deal with the fact that it has a hinge.

  5. In my projects when I need reference semantics I reach for final class and when I need polymorphism I reach for protocol, basically never using class. There's been one exception in recent history and writing this post is making me curious to go back and try to grasp why I thought I needed class inheritance in that case.

  6. Points 4 and 5 together mean that my programatic home cutlery set currently is full of folding forks :confused:

  7. I understand that just because I've been able to solve all of my polymorphism problems thus far using protocols doesn't necessarily mean that protocols are always sufficient. If someone could give me an example of where class-style single-inheritance is required that would be great.

  8. This concurrency effort that we're all posting about exists because we currently have classes and we wish they were statically enforced actors instead. Is it fair/correct to say that this need for our classes to become actors arises 100% from the reference semantics aspect of classes, and 0% from the class-style single-inheritance aspect of classes?

  9. As I said, I try to only use protocol for polymorphism, and so far that's worked very well. The result of this is that I never use class, only final class, the advantage of which is that if actor comes without class-style inheritance but with the ability to conform to protocols then I can use it to protect all usages of reference semantics in my app without needing to rewrite any of my polymorphism.

  10. I would love to replace all of my folding forks with the more simply conceived actor concept.

  11. Perhaps then the question is, am I lucky/sheltered for thus far having managed to forgo class inheritance entirely, or am I simply good at using protocols?

  12. Going back to point 5, can someone help me get clear on a situation in which class inheritance is the only way?

  13. If the answer to points 11 and 12 is that class-inheritance is a tangled legacy concept and not actually fundamental to the practice of architecting software then I would offer the following argument:

  14. A project's Swift source code breaks only when there is a "breaking change" to the Swift compiler.

  15. A project's architecture, by contrast, suffers a soft breakage when there is an additive change to the Swift compiler which opens up a desirable new direction, but the current architecture is getting in the way of going there.

  16. We all agree that there is a major obligation to minimize breakage of preexisting Swift source code.

  17. What cultural guidelines do we have in place regarding the breakage of architecture, as I've defined it above? Should we alter new features not to make them more useful in a fundamental sense, but to make them easier to transition to in a practical sense?

  18. I believe the answer to that is: definitely sometimes that is of course what we do.

  19. In fact, would it be fair to say that that is exactly the reason that class exists in Swift in the first place? Because there was so much legacy architecture in C/Objective-C which demanded a concept like class that it seemed worthwhile to create an analog in Swift? (I'm out of my depth with regard to C/Objective-C so maybe that's wrong).

  20. And perhaps we could say that the only reason that there isn't a standalone reference semantics type declaration in Swift is that the folding fork final class has been able to pick up the slack?

  21. If that's right, maybe we can view class as the scar tissue which eased the transition from the previous languages. I would hope though that we would do our best to minimize the extent to which inevitable language scar tissue continues to propagate into next-generation features.

  22. Apple has been officially pushing protocol-oriented-programming for years, and since my personal experience with it has been quite successful, I'm hoping that we decide to mark this as the moment to begin to officially move away from classes.

  23. I've littered the post with "maybe"s and question marks to emphasize that although I'm giving a lot of opinions in a somewhat strong manner, I'm aware and want any given reader to be aware that I'm not sure that this is all sound reasoning.

  24. Thanks for reading.

-Jeremy

7 Likes

Personally, I can't identify any, but I can identify 2 specific scenarios where the ergonomics of class inheritance are (imo) superior to the ergonomics protocols. One of these scenarios is a design where all subclasses (or conforming types) should have a stored property with a default initial value, consider this example:

With protocols, that would look like this:

protocol Shape2D {
    var center: SIMD2<Float> { get set }
    var color: Color { get set }
}

final class Box: Shape2D {
    var center: SIMD2<Float> = .zero
    var color: Color = .red
    // Box specific stuff goes here.
}

final class Circle: Shape2D {
    var center: SIMD2<Float> = .zero
    var color: Color = .red
    // Circle specific stuff goes here.
}

final class Triangle: Shape2D {
    var center: SIMD2<Float> = .zero
    var color: Color = .red
    // Triangle specific stuff goes here.
}

With class inheritance:

class Shape2D {
    var center: SIMD2<Float> = .zero
    var color: Color = .red
}

final class Box: Shape2D {
    // Box specific stuff goes here.
}

final class Circle: Shape2D {
    // Circle specific stuff goes here.
}

final class Triangle: Shape2D {
    // Triangle specific stuff goes here.
}

The class inheritance version is clearly more compact, and the behavior between the two versions is nearly identical. The difference is that, in the protocol version, center and color can be computed in conforming types instead of stored, but in this contrived example, I don't require that flexibility (Edit: This isn't even a difference, because one could override if they really wanted one of the properties to be computed). The disparity between the two ergonomically becomes greater and greater as the initialization of the base class becomes longer and/or more complex.

Anyhow, my point is that I don't think there is an example where class inheritance is absolutely 100% necessary, but as it stands currently, there are arguably scenarios where class inheritance is ergonomically superior compared to protocols. That being said, I'm also not saying we need inheritance for actors.

I was thinking of mentioning this in my first post but it was already long enough - the ergonomic benefit of classes in my opinion is minimal at best and actively harmful at worst. I say minimal at best because if a protocol's required properties balloon it can always package them as a single required property of some composite type and then expose all of the sub-properties in an extension as if they were declared by the conforming concrete type. I say actively harmful at worst because I can imagine an argument to the effect of that while we are in support of boiling down protocol property requirements to just one, we are actively against the idea of eliminating it down to 0, because... something to do with local reasoning? I haven't thought that one through very clearly, it's just a feeling... so mainly my point is that since protocol property requirements can generally be boiled down to a single requirement, I, like you, am not very convinced by the ergonomics argument.

This is really interesting. Protocols are designed today to be concretely implemented by either a struct/enum or a class. Would we have to design our protocols differently if they are to be implemented by an actor? Looking at the isolation proposal it seems methods that only use immutable members could be used to implement a protocol. I wonder how constraining this will be and whether it will be possibly to implement many existing protocols that exist in projects, or whether these protocols will have to be rewritten due to how actors must be interacted with asynchronously.

If I implement a common pattern of a protocol that has two implementations — a real one and a mock one for automated tests, will it be easy to have the real one be implemented by an actor and the mock one implemented by a class? Or will both have to be actors?

In other words, can a class instance always substitute for an actor instance? It seems I’d want to.


An actor is a way of implementing an isolated piece of behavior, but it’s not the only way. It feels like the proposed actor model is bundling a lot of concepts together. It’s a class++. Having used Elixir’s GenServer the biggest concept I found is that message-passing is at the core. A GenServer is an instance of something you can send and receive messages from, and these message are something that can be captured, replayed, discarded, persisted. This message passing concept feels missing from Swift’s actors. The primary benefit seems like it is safe message passing and safe encapsulation. I wish this was more front-and-centre.

That only gets you so far if there is some computation involved to initialize the properties:

Properties package:

struct Shape2DProperties {
    var center: SIMD2<Float>
    var color: Color = .red
    
    // We've introduced some intialization computation.
    init(centerMin: Float, centerMax: Float) {
        center = .random(in: centerMin...centerMax)
    }
}

With protocols:

protocol Shape2D {
    var properties: Shape2DProperties { get set }
    init(centerMin: Float, centerMax: Float)
}

final class Box: Shape2D {
    var properties: Shape2DProperties

    init(centerMin: Float, centerMax: Float) {
        properties = Shape2DProperties(centerMin: centerMin, centerMax: centerMax)
    }
}

final class Circle: Shape2D {
    var properties: Shape2DProperties

    init(centerMin: Float, centerMax: Float) {
        properties = Shape2DProperties(centerMin: centerMin, centerMax: centerMax)
    }
}

final class Triangle: Shape2D {
    var properties: Shape2DProperties

    init(centerMin: Float, centerMax: Float) {
        properties = Shape2DProperties(centerMin: centerMin, centerMax: centerMax)
    }
}

With inheritance:

class Shape2D {
    var properties: Shape2DProperties
    
    init(centerMin: Float, centerMax: Float) {
        properties = Shape2DProperties(centerMin: centerMin, centerMax: centerMax)
    }
}

final class Box: Shape2D {
    // Box specific stuff goes here.
}

final class Circle: Shape2D {
    // Circle specific stuff goes here.
}

final class Triangle: Shape2D {
    // Triangle specific stuff goes here.
}

This becomes even more dramatic if I just introduce more 2D shapes, let's say I have 20 in total. Now if I ever wanted to change that initialization behavior in a refactor, I would need to copy and paste the new initialization behavior 20 times, instead of just zero. And sure, I acknowledge the danger here, maybe I didn't realize that some change to the init of Shape2D would change the behavior of Triangle, but in a case like this, it feels like a good trade to me, and is something I wish we had with protocols.

This is a fair argument, but it kind of breaks down if you consider default implementations of protocol requirements in protocol extensions, because there is little local reasoning there, conforming types get a default implementation that resides in the protocol extension. I think that is quite similar to the concept of a subclass getting "default implementations" of properties, initializers, and methods of its superclass.

I think your example just further serves to highlight the advantage of avoiding class-style inheritance, because it is the only reason for the initializer problem you describe. If I were to use struct in your example instead of final class then we wouldn't have any issue:

struct Shape2DProperties {
    var center: SIMD2<Float>
    var color: Color = .red
    
    // We've introduced some intialization computation.
    init (centerMin: Float, centerMax: Float) {
        center = .random(in: centerMin...centerMax)
    }
}

protocol Shape2D {
    var properties: Shape2DProperties { get set }
    init (properties: Shape2DProperties)
}

extension Shape2D {
    init (centerMin: Float,
          centerMax: Float) {
        self.init(
            properties:
                Shape2DProperties(
                    centerMin: centerMin,
                    centerMax: centerMax
                )
        )
    }
}

struct Box: Shape2D {
    var properties: Shape2DProperties
}

struct Circle: Shape2D {
    var properties: Shape2DProperties
}

struct Triangle: Shape2D {
    var properties: Shape2DProperties
}

As far as I understand, the thing that allows structs to have such wonderfully ergonomic initialization rules compared to classes is that structs don't have to worry about class inheritance - therefore, I think it follows that initialization rules for actors could be as simple as they are for structs if we don't need to worry about inheritance.

6 Likes

Ok, I'll give it to you, it's almost as good here, assuming I can get away with my shapes being value types. It's still somewhat problematic that I would be exposing "properties" and a bunch of computed properties to access each individual property, and I'd argue the steps needed to get an almost as good solution compared to inheritance are less than intuitive:

Step 1: Collapse all properties into a single type.
Step 2: Add computed properties to the protocol extension to get access to each individual property.
Step 3: Add a default initializer to the protocol extension. (only viable if conforming types are value types)

I don't think so, I think it has more to do with structs being value types, so I'm pretty sure (not 100%) that this would not work for actors, which takes us back to 20 different initializers...

Assuming we buy into my earlier definition of class as being reference semantics + class-inheritance, then I suppose we've constrained our search: the complexities of class initialization are either due to the reference semantics or due to the inheritance. I stated that I think it's the inheritance, and you're saying you think it's the reference semantics. I'll admit I can't say with certainty, but my intuition heavily points me to believing its the inheritance. Hopefully someone who knows with authority can chime in?

Additionally I'd like to know, if it indeed turns out to be the inheritance, does this mean that actors without inheritance could enjoy both of these?
a) Synthesized initializers
b) Lack of convenience keyword and all of the hassle of the designated initializer concept

3 Likes
  • What is your evaluation of the proposal?

+0.5

Managed thread-safety will be a huge benefit to Swift and its users, but I find even this model, reduced though it may be from earlier pitches, to be more limited than it seems to need to be.

As an aside, the proposal could do with a summary of the proposed solution before diving into definitions interspersed with examples. Even knowing what actors are does not prepare you for the specifics of Swift's implementation.

Positives

Compiler Checked: Obviously the biggest benefit of this proposal is the compiler checked thread-safety. This will be a huge benefit to the community, not only in the safety itself, but in performance, as it will replace manual DispatchQueue usage and even locks in many cases.

Discoverability and Teachability: Adherence to Swift's existing class model will ease developers into actors and provides a clear evolutionary path for classes which may eventually need thread-safety. This allows developers to reuse existing knowledge and skills in building these types. This adherence also makes the feature much easier to teach, as you can start with classes, illustrate the issues around thread-safety, and evolve to an actor. You can even illustrate the functionality of an actor within a class before moving on to the real thing. This should make actors much easier to adopt, accelerating usage across the ecosystem. Keeping class capabilities also provides a more complete set of functionality, especially inherited state, that is important for many actor use cases.

A Model We Know: In many ways, the actor model presented in the proposal works like the DispatchQueue safety model used in Swift and Objective-C already. Given this fact, users should be able to transition their mental model to actors rather quickly, delta the particulars of actors. (In fact, I preferred the use of "queue" rather than "mailbox" to describe the actor's internals for this reason. Rather than confusing I found it a familiar way to describe the actors' main safety mechanism. And since Swift actors are not addressable, I don't believe the "mailbox" metaphor works well and is less accurate anyway. In fact, it seems likely the community will eagerly adopt the "queue" metaphor in blog posts and social media, so the proposal may as well go along.)

Negatives

Async by Default: In following the various pitches and this proposal, I've never seen a complete explanation for why this model starts with async only access to even read-only state, aside from an implicit "that's how actors work". Take this mention from the proposal:

Even non-mutating access is problematic, because the person's name could be modified from within the actor at the same time as the original call is trying to access it.

This is clearly the case, but the proposal makes no mention as to why we can't employ the obvious solution to this problem, a lock, to provide synchronous access. Historically, actors were created to eliminate the need for the locks (and to supported more distributed systems), but the 50 years of hardware advancement since then have made locks nearly free. It seems like this would be especially true when the concurrency system is being provided by the language itself. Of course, this may just be a limitation of the current proposal; I haven't kept up on the isolation features since they were removed (and they were very hard to understand). In fact, in one of the early pitch threads it was suggested that manual locking could be a solution to the synchrony problem, I'd just have to provide it myself. If that's the case I don't see why Swift couldn't do it for me since that would be faster and less buggy anyway.

Being able to offer safe, synchronous APIs is an important feature. Like I mentioned above, if actors work like types already familiar to Swift developers, it makes the feature far more approachable. It's far more discoverable if users don't have to radically change the way they use actors. It's far more teachable if it leaves as much of the existing Swift programming experience alone. Finally, synchronous APIs are important to the overall API design experience of Swift. We can see an example of this design impact in the proposal.

func primaryOwner() -> Person? { return owners.first }

Now, in typical Swift this would obviously be expressed as a computed property.

var primaryOwner: Person? { owners.first }

However, due to the actor's async access requirement, it can't be, if we want to offer this value publicly. Of course, if we gain property effects, this issue is somewhat mitigated, but it would still require an apparently unnecessary use of async. I say "apparently" because unless the user understands why actors require async access, they won't understand the necessity. And this proposal hasn't offered a justification for it either, other than an implicit "that's how actors work". (Which seems like a circular justification to me.)

Any change to how we fundamentally write Swift has a high justification cost. I don't believe this proposal has justified that cost.

Only Kinda Actors: I think this proposal should more thoroughly describe the Swift version of an actor how it differs from actors in other languages, as well as from the general definition. As far as I can tell they're rather different in that Swift's actors aren't parallel by default, aren't distributed, don't send messages, aren't addressable, and don't operate solely on other actors.

  • Is the problem being addressed significant enough to warrant a change to Swift?

Yes! I manually implemented essentially what's described here in Alamofire 5 using shared serial DispatchQueues and locks. It wasn't easy, and even with the thread sanitizer, probably isn't completely safe. Being able to replace it all with a model built into the language will be great, even if it does mean another rewrite.

  • Does this proposal fit well with the feel and direction of Swift?

In many ways, yes, but falls short, especially in the shape of APIs you can create with it. As I wrote above, I don't believe the proposal has gone far enough to justify the radical shift in API design that would be required for Swift developers to adopt the suggested model.

  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

N/A

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Somewhat in depth from the Swift side of things. I've attempted to keep up on all the pitches, participated in the threads, and have implemented some actors using the various snapshots.

3 Likes

To speak broadly, protocol implementation is about stating conformance to a set of stated behaviors, while class inheritance is about importing a particular implementation of behavior.

Class inheritance can be either seen as more brittle or having a unique benefit. A wider variety of changes can be made through class inheritance than through protocols, meaning more significant behavioral changes can be potentially made without coordinating with implementors.

Generally I've seen people gravitate toward class inheritance for a "plug-in logic" anti-pattern, where subclassing is used to add custom business logic or behavior to an object with primarily private behavior around integration and life-cycle management. This can usually be solved with a delegate/datasource protocol pattern on a wholly externally separate type for customizing behavior. One argument why using protocols is better here is specifically in the name protocol (vs interface) - the idea that they are contracts for what is expected and what you are capable of doing. With class inheritance, you wind up melding a lot of independent concerns together, and strict change control on such protocol "contracts" requires additional dedication/process.

2 Likes

On the inheritance conundrum, I tried to propose an alternative to full class-like inheritance here. Basically, we could still have inheritance of storage (which covers most of the meaningful use cases for inheritance) without adding override and super.

But I'd like to make a note on the "Swift is multi-paradigm" argument for implementing actors that are similar to (current) Swift classes. While Swift is technically not married to any particular paradigm, it is also, and luckily so, not just an unrecognizable blob of random ideas borrowed from other languages.

Swift is not particularly fit for that brand of OOP that makes heavy use of inheritance (think Java), because it lacks 2 key features required from that paradigm: abstract classes and protected access. There's no way around it, and there's plenty of threads in this forums in which people with a traditional OOP background try to translate these ideas in Swift and are suggested to think in a different way. But Swift is also opinionated (again, luckily), and some of its features are consistent with different ways of thinking about problems: one key way is that "protocol-oriented programming" idea, that is actually supported by many features of Swift protocols, like associated types, the use of Self in protocol definitions, and retroactive modeling.

For an easy comparison, take for example Kotlin, a language that has nothing to do with Swift, but seems similar on a surface level: Kotlin has none of those features for its interfaces, but on the other hand it has abstract classes and members, so it's better suited for OOP, and in fact in Kotlin most designs have to rely on OOP patterns.

Also, some features that are currently lacking in the Swift model for protocols, like generalized existentials, are frequently discussed, the core team is working on them, and will provide even more abstraction power to Swift when working in a protocol-oriented way. This is the "opinion" Swift is developing over time, together with other "opinions" like heavy reliance on value semantics.

Thus, "Swift is multi-paradigm" is not really an argument in favor of a feature like inheritance of actors: I'm personally neutral towards it, because I don't care, I will only ever write final actor, as I currently do with classes. But it seems to me that the burden of proof is on the side of needing inheritance, not on the side of skipping it, at least for now.

Being a completely new feature, that especially has no ties to the Objective-C legacy, I think it could actually consider new ideas, like inheritance of storage, and only storage, which, as far as I can tell, seems to cover 99% of all use cases that I've seen in these forums over the years, and that I've personally found meaningful (also inheriting the rest of the interface, of course, but without override).

12 Likes

Thanks everyone! The core team has decided to return this proposal for a second round of review, which I have initiated in this thread:

In response to the feedback from this thread, the core team has decided to leave out inheritance for actors until more real-world experience can determine its need. For the second review, we would like to put some more focus on the topic Chris Lattner brought up about lets and whether they deserve special consideration as nonisolated-by-default declarations.

11 Likes