[Amendment] SE-0306: Actors and `nonisolated let`

Hi everyone. We are starting review for an amendment to the accepted proposal SE-0306: Actors today, and running the review through May 28, 2021.

This amendment is motivated by having more hands-on experience incrementally adopting actors into an existing app. Immutable object state is very common, and as originally accepted, the proposal requires even immutable let properties in actors to be treated as actor-isolated unless explicitly marked isolated. The proposal authors feel that in practice this creates too heavy of an annotation burden when turning existing code into actor code, since every immutable value must either be marked nonisolated or await-ed everywhere. There were however legitimate concerns about library evolution in the original review if public lets would be implicitly exported as nonisolated by default, since that would limit the library author's ability to turn the declarations into locally-mutable state or computed properties with isolated computation in the future. This amendment seeks to balance the competing considerations by allowing let properties on actors to be accessed without isolation locally within a module, but not across modules unless the declaration is explicitly nonisolated.


Reviews are an important part of the Swift evolution process. All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to the review manager or direct message in the Swift forums).

What goes into a review of a proposal?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift.

When reviewing a proposal, here are some questions to consider:

  • What is your evaluation of the proposal?
  • Is the problem being addressed significant enough to warrant a change to Swift?
  • Does this proposal fit well with the feel and direction of Swift?
  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Thank you for helping improve the Swift programming language and ecosystem.

Joe Groff
Review Manager

28 Likes

I very much endorse this amendment, and would like to add some experiential anecdata.

I've spent a lot of time over the past few weeks doing trial ports of existing projects over to the new concurrency model, and helping others do the same. I tried out the new compiler enforcing nonisolated for lets on my nursery of converted apps and the results were uniformly one of frustrating error squashing busywork.

Changing code to use the new features is, for the most part, a very fulfilling experience, leaving the code far more elegant than it was before. It's also often a very natural conversion that does not involve major refactors. It is surprising just how much Swift code is already structured in a way that naturally converts to both async/await and actors. Threading a completion handler through a bunch of calls? They can all become async and the surrounding code gets a nice clean up. Got a class that protects its state with a queue? That's an easy conversion to an actor.

That migration does involve some patience and care though. It is not purely mechanical, even when the code is already nicely suited to the model. Adding async or actor generates errors. Those errors help guide you to where you need to apply some of the new features.

But the errors can be intimidating, and sometimes the user gets into a vicious circle. They react to the errors generated by making more functions async, or by putting more things on the main actor. This leads to more errors and the user quickly becomes overwhelmed. At this point they may just give up with the migration.

For this reason, we need to be very circumspect about presenting errors that will seem unnecessary to a user. Every compilation error we show them needs to carry its weight. And telling a user that e.g. their .shared singleton instance of a controller they just converted to be on the main actor cannot be accessed until they put this seemingly unnecessary keyword on it has a high cost. Too high in my opinion.

And when I look at the reasons that have been cited for why it is necessary to make them jump through that hoop (ah but what if you wanted to convert that let to a var in the future?), I do not think they would stand up to scrutiny by users demanding an explanation for why the language is making them hang more keywords on their code.

I think we must acknowledge something: many users of Swift love it, but have this nagging feeling that it is skewed a little too much towards pedantry and not usability. I usually disagree, and think the case for how the language works (such as eschewing implicit conversions) is the correct one. But I am mindful whenever I would find it difficult in good faith to defend the compiler strictly enforcing something. This is one of those times.

Doug’s proposal, limiting access without nonisolated to the current module, seems like the pragmatic solution here.

29 Likes

Is .shared Sendable? If it’s not then why it’s a fixit such a high cost?

I am only in favor of implicit nonisolated for Sendable properties. Otherwise as I reader/code reviewer I want to know when something is nonisolated.

For a cross-actor reference to an immutable property, the property type must conform to Sendable.

1 Like

Great change! :+1: Very much in the spirit of Swift, IMHO.

It is hard to argue against a change that (a) actually succeeds at squaring two competing demands that initially seemed at odds; (b) corrects an ergonomic issue borne out of hands-on usage experience; (c) promotes progressive disclosure and therefore teachability; and (d) does so in a way that falls naturally along the existing public-versus-internal boundary rather than inventing a new arbitrary divide. Although it does push a (fairly big) exception to the everything-via-mailbox actor paradigm, this is the pragmatic part of Swift's philosophy at its best, in my view.

17 Likes

What kind of existing apps and projects are we referring here at? Are those Apple internal projects? It's hard to believe that anyone else would use these features yet, other than tinkering with the nightly snapshots a bit.

+1, I think this is a pragmatic middle ground. I'm thrilled that this solves the usability problem without interfering with library evolution.

8 Likes

I think it makes sense that accessing public let from different modules remains isolated by default. What about accessing a public let from the same module? Do we need await in that case?

It's a bit weird but overall a good tradeoff given Swift's other across module semantics and library evolution needs.

I'm +1 on this amendment.

+1. Nothing more to add.

+1

It feels natural to me.

No, you do not need await in that case. Since both the definition and use are within your own module, you can change them at the same time.

Doug

2 Likes

My experience is that tinkering with a sample project (you could pick one of Apple's sample projects that uses dispatch queues) is enough to hit this problem. For example, any use of the static MySingleton.shared pattern when you convert a class to an actor is likely to encounter it.

3 Likes

Thanks everyone for participating in the review! This amendment has been accepted.

3 Likes