[Pitch 2] Default actor isolation per-file

I'd very much support wording change of the setting to be honest. I was missing some "default what" in the previous just using nonisolated a bit -- this would make it more self describing.

There's been some discussion against using nil that some were concerned about. IMHO the nil is fine, but if we want to lean into it we could be saying using defaultIsolation(nonisolated) couldn't we? The defaultIsolation(MainActor) is equally special, because normal swift code would require a .self there after all. So that's another slight improvement to consider.

4 Likes

using followed by an attribute sounds like something that could be generalized to other attributes. So its future direction would be something akin to:

using @MainActor
using nonisolated
using @discardableResult
using public
using @const
using @objc

Except for access modifiers (public, private), I'm not really sure this makes a lot of sense.

Whereas using followed by a compiler flag sounds like something that can be generalized to other compiler flags, such as:

using defaultIsolation(MainActor)
using defaultIsolation(nil)
using enableUpcomingFeature(BareRegexLiteralSyntax)
using enableExperimentalFeature(MoveOnlyPartialConsumption)
using warnImplicitOverrides
using nostdimport

If we go this route, then using should probably be right at the start of the file, otherwise things like enableUpcomingFeature(BareRegexLiteralSyntax) won't be able to work (I assume) since they affect parsing.

3 Likes

I have a use case for generalizing using to support another attribute: @available. For example, it could be useful to be able to write using @available(macOS, unavailable) and have that apply to all the top-level declarations in a file that is dedicated to code that should be unavailable on macOS. I can also imagine the need for this arising more often if the language were to support custom availability domains.

19 Likes

... Fair enough. I didn't consider using as a function that accepts a trailing closure because they aren't allowed at the top level (except for main.swift), but it would admittedly be confusing to have using { } meaning different things in different contexts.

Oh, very nice idea!

Doug

7 Likes

It is shading closer to what we've previously used shorthands on extension for, like public extension for a set of public APIs.

I don't recall offhand if @available(...) extension propagates availability to all members but it's certainly a reasonable user expectation.

(And on that note, do or should we support @MainActor extension...?)

Yes, @available attributes on an extension do propagate to the members of the extension.

I think that the term you used here: "applying" or the more imperative "apply" would better describe the mechanism than "using" does. As you said: "...apply to all the top-level declarations in a file ".

This has the advantage of not overloading a term that is already used in other languages with other meanings, as others have mentioned.

2 Likes

I am not sure how I feel about the keyword using. Some languages use it for import or namespace related things, so it might be surprising for folks coming from these languages. I wonder if alternatives like define or set should be explored, especially if we land on a pattern that requires an additional key like defaultIsolation .

1 Like

I’m not against, +0.5, but the “using” keyword could become more prevalent. I think this should be used for distributed actor systems too, for consistency. And be spelled in a way so that future per-file defaults can be introduced in the future too. For example:

using(isolation) nonisolated

I'm both intrigued and terrified by the idea I'm about to suggest:

// affects file scope
private using ...
fileprivate using ...

// affects entire module
internal using ...


// affect public interfaces (I think this would be terrible for readability, including it here for completeness's sake
public using ...

It reminds me less of using from C++ and its relatives, and more of use from Perl and JS (not a keyword, but "use strict";). Not that those are necessarily great languages to take inspiration from in the general case but in this case I think it's okay.

3 Likes

That's a pretty good point—I do wonder if the "-ing" is getting us anything here, and if not whether we ought to adopt use.

7 Likes

I like “use” more as well. It’s a more authoritative phrasing

2 Likes

Good point -- after all it's import MyPackage, not importing MyPackage

4 Likes

use foo makes me think that the action happens at the point the line occurs, whereas using foo makes me think it's describing a property of the file as a whole. So for me, using seems to match this case better. That may just be a personal reading, and my C++ brain kicking in.

1 Like

An idea could be to make a using <attribute> followed by a colon to propagate to the declarations that follow. For instance we could have:

using @MainActor:   // applies to the declarations that follow

func a() {}   // @MainActor
class A {}    // @MainActor

using nonisolated:  // replaces @MainActor above for the declarations that follow

func b() {}   // nonisolated
class B {}    // nonisolated

This could support more attributes. It could also be used at other scopes than file scope (in which case it'd stop to apply at the end of scope):

class B {
    using @MainActor:
    func a() {}
    func b() {}
    func c() {}

    using nonisolated:
    func d() {}
    func e() {}
    func f() {}
}

I suppose we could also get rid of the word using and just leave the colon (like attributes in D and access modifiers in C++), but I fear that might be a bit too subtle.

1 Like

Yes you're right, writing multiple conflicting using declarations is invalid. I don't think the sectional style is the right choice for Swift because:

  1. Swift code is generally not sensitive to declaration order. For example, we don't enforce that declarations are written before they are used in a file. import declarations also have this same file global behavior.
  2. I don't think it's necessary; you can always write whatever attribute you need on the declarations in that file when you want that behavior.

I will clarify this in the proposal, thank you for pointing it out!

We could do this. I'm not convinced it's necessary because I think people will naturally write using declarations at the top of the file, just like people do with import statements. We don't force people to write import declarations first, but they typically do for similar reasons.

For what it's worth, it will also be easy for diagnostics involving inferred actor isolation to point at where the using declaration is if the isolation was inferred from one in the file.

I was originally skeptical that something like this was necessary, too. But as I've adopted SE-0466: Control default actor isolation inference, I've found that it's super common to write a collection of related types used in concurrent code in the same file, and it's very annoying to explicitly write nonisolated on every single one of those types. Not everybody writes code like this - it's also common to stick to one type in each source file, and if you write code that way, you probably don't need this feature, because you'll have one nonisolated annotation per file that includes a type used from concurrent code either way.

I think this change is simpler than most of the isolation inference rules we have already. The isolation inference rules that are difficult to reason about are when they are inferred from a protocol conformance/requirement or a superclass/overridden method, especially because the protocol and superclass rules can involve chains of protocol refinement or class inheritance, respectively. I think we need better tools to understand the source of actor isolation inference, independent of this change. The compiler already keeps track of it and surfaces the information in some limited cases, but that information needs to be plumbed through more tools like SourceKit's cursor info request (which is used by IDE tools like Quick Help in Xcode and Hover in VS Code).

Yes! This goes right along with what I wrote above about surfacing the source of actor isolation inference in more places. We'll need to be careful about not overwhelming people with this information when they don't need it -- like in MainActor mode when you don't have any concurrency at all, you shouldn't really need to care -- but it should be available to people who want to see the isolation.

The proposal is that using only ever applies to file scope. People will learn that when they first encounter using, and I don't think they will need an explicit reminder in the syntax every time it's written. If we ever extend this in the future to be allowed in more limited scopes within the file, I think it'll be clear that it only applies to the lexical scope where it's written, because Swift programmers have already internalized lexical scoping rules.

I'm sympathetic to this, but I'd rather fix this problem once and for all with keyword documentation (accessible through cursor info) than change how we design keywords. Other keywords have this problem too, like any, some, as, in, etc.

4 Likes

I agree; I've added a section on Using Swift package manifest-style APIs for specifying default attributes to the alternatives considered. Please let me know what you think!

I'll also work on adding the other clarifications you mentioned to the proposal, including the behavior when you specify an unsupported attribute, and some of the implications of extending using to other things in the future.

Reading that section made me think that maybe there are two features here:

  1. Implicit attributes/modifiers
  2. Modified compiler settings

Part of me thinks that separate syntax might be useful.

Combining that with some of the other ideas from the thread, maybe something like this?

implicit @MainActor
implicit @available(SwiftStdlib 5.1, *)

and

use defaultIsolation(MainActor)
use enableUpcomingFeature(BareRegexLiteralSyntax)

It's a bit odd that there are two ways to control default actor isolation. But they are both based on existing concepts, so I think I'm OK with it.

3 Likes