In that case this issue might also be interesting!
Is there a list of all breaking changes currently being considered for Swift 6? If not, will it be published any time soon?
We were just talking about global actor inference in another thread, and it was suggested we bring the suggestion over here:
I would like to propose removing all global-actor inference in Swift 6.
Global actor inference has an extremely complex set of rules, including some extremely surprising ones (such as the "property wrappers" one that is the original subject of that thread). It creates "spooky action at a distance" where conforming to a new protocol, or using a new property wrapper, might completely change the global actor of a type (and all its members).
This kind of source-breaking change might be rough on consumers of frameworks like UIKit, but I believe that it will lead to better understanding of the actual actor isolation requirements of the code that people are writing, as well as fewer hidden surprises and gotchas.
Given our guidance that source-breaking changes need to be targeted, not sweeping, I think it's fair to say that removing all global actor inference would be exceedingly unlikely.
However, the thread you linked to discusses a specific inference rule and makes particular arguments about why it in particular merits reconsideration even as it's source breaking; perhaps someone who's familiar with those points might enlighten the community about it here, or otherwise those interested can pop over to the other thread to learn more.
Sure, I'm happy to summarize here, since I made the original post. In brief:
This struct is not actor-isolated:
struct MyView: View {
@State private var x = 0
var body: some View {
Text("\(x)")
}
}
This struct is actor-isolated (to the main actor):
struct MyView: View {
@StateObject private var model = SomeModel()
var body: some View {
Text("\(model.x)")
}
}
Simply changing @State
to @StateObject
changed the actor isolation of the entire containing type. It's because struct StateObject
is declared @MainActor
, but struct State
is not. Applying an actor-isolated property wrapper to a property implicitly propagates that isolation up to the containing type, which in turn propagates it to all sibling declarations of the property (including all other properties and functions within the type).
I made a more complete argument in the other thread, so I'll just summarize here. This upward propagation of actor isolation from a property to the containing type is not well justified by SE-0316 where it was introduced, and has been the source of a lot of confusion (to mention just a couple recent examples). It even inhibits the adoption of @MainActor
annotations in some places, because the actor-isolation becomes "viral", spreading further than the author intended. Nobody has commented in favor of the current behavior; the confusion and surprise is universal in all comments.
Changing this behavior (no longer propagating the actor isolation to the containing type) would be source-breaking, but there may be ways to mitigate that breakage for particular frameworks (e.g. SwiftUI). But since it's source-breaking, it would need to happen in a new language mode, so Swift 6 would be the right time to do it.
Would it be appropriate to open an evolution proposal now for things that might change in Swift 6? Or should that wait, since we're still firmly in Swift 5.8 development?
Concerning data-race safety by default, one thing currently missing in Swift's concurrency support is interop with other systems that handle their own multithreading environment, like Combine, or RxSwift.
For example, both Combine and RxSwift have a way to subscribe to events on the main thread. Getting a closure marked @MainActor
is easy, but to be able call it without error or warning, the place calling it also has to be statically @MainActor
. Retrofitting that in an environment managing threads dynamically it is not easy. I guess the easiest would probably to have Swift provide something like @unchecked @MainActor
, or a MainActor.runInline { ... }
that aborts if not already on the main actor. Currently you can probably hack something using @MainActor(unsafe)
but it's not its intended use case, and the (unsafe)
part of it is supposed to be ignored by Swift 6.
(By the way Task { @MainActor in ... }
does not run the code directly but queues it to be run later so it won't work here, and most of the time you are not in an async context so you cannot await anything inline.)
Closures passed to DispatchQueue.main
are considered @MainActor
so I thought there was already a solution for this problem, but looking at Swift's source code I realised the handling of DispatchQueue.main
was just a hack in the compiler. Combine is an Apple technology so if needed I'm sure you could always also add special support for it in the compiler, but something that could be used by third parties would be really useful. I'm sure most people using RxSwift or something similar would like to not to have to migrate out of it to fully migrate to Swift 6 (the library used needing modifications is fine of course).
Yes, it’s fine to make new evolution proposals now.
Swift 6 sounds like a good opportunity to do something about the fact that Decimal
loses precision unexpectedly when initiated using a literal since all literals with a period in them pass through a binary floating point stage which is not what one expects when specifying a number that is decimal in its nature.
I don't think that would need a whole new language mode. When we have a design and implementation for decimal literals, I would say we should adopt it even in older language modes, since it should have the same behavior as today for binary floats, and the new behavior would be a bug fix for existing code that uses literal syntax with decimal types.
I agree, I would just hate to see it not being implemented because it would be source-breaking due to a "space bar heating" aspect.
Please do create a proposal for this. I think you have a strong case for removing this inference based on its practical impacts, and it needs to go through the process.
Doug
Yeah, I’ve been working on a proposal. I hope to have a pitch up sometime next week, and I already have an initial implementation. Thanks for the feedback!
I hope to have a pitch up sometime next week
Pitch has been posted here.
This section of the original post clearly encourages developers to opt-in to "upcoming" features early on. SE-0362 lists 6 feature identifiers, but if I understood it correctly, all future proposals, including the recently accepted SE-0384 will have to follow this unified approach and provide a "feature identifier". But I can't find one mentioned anywhere in SE-0384, instead, it introduces a "frontend flag" named -enable-import-objc-forward-declarations
.
While this might just be an oversight, my more general question is:
How are developers supposed to easily discover all available features that are opt-in during Swift 5.x and will be turned on in Swift 6 for a "piecemeal adoption"?
Practically speaking, I think it's important that there is a place with an overview of all of these feature flags with a quick summary what they are about and maybe a link to the full proposal. Is there already such a place or Swift command or something else I can point people to / use myself?
Currently you need Swift 5.8 (most recent Xcode beta) to use this feature. It will probably be better documented once 5.8 is out. For now you could look them up in swift/Features.def at main · apple/swift · GitHub
I don’t think the SE-0384 feature you mention is currently supported by the new upcoming features option.
If you play with this, SPM has support under .swiftSettings
to add upcoming or experimental features in the package if you change the package version to 5.8.
This should clearly be done with a feature identifier, and we'll look into fixing this before it's released. Thanks for pointing it out!
As for listing out all the feature identifiers, yeah, that's something we've been looking into for the website.
Just to round-trip on this: the Language Workgroup has summarily revised SE-0384 to use an SE-0362 feature flag instead of an ad hoc flag.
I checked the file, but something seems off: I can only find these lines in there, but there's no mention of StrictConcurrency
which was explicitly mentioned in SE-0362 to be available as an "upcoming feature". The same goes for ImplicitOpenExistentials
. Am I missing something?
One more source-breaking change that I'd like to mention in case I forget: if you switch over a non-frozen enum and forget to include a catch-all clause, that is currently only a warning (and a trap at runtime if it ever encounters an added case) rather than a compiler error:
Example:
func test(_ x: FloatingPointRoundingRule) {
switch x {
// ^ warning: switch covers known cases, but 'FloatingPointRoundingRule' may have additional unknown values, possibly added in future versions
// ^ note: handle unknown values using "@unknown default"
case .up,
.down,
.toNearestOrEven,
.toNearestOrAwayFromZero,
.towardZero,
.awayFromZero:
print("foo")
}
}
It seems the original goal of SE-0192 - Handling Future Enum Cases was for this to be an error, but the rollout was considered too aggressive for Swift 5.0, so it was softened to a warning + runtime trap:
Honestly, I hope enough time has passed that we can make this change in a Swift 5.x update, but if not, let's please finally do it in Swift 6.
CC @jrose