Swift Evolution Monthly: December '22
Registry auth, Opt-In Reflection, if-switch Expressions, Vision documents, DiscardingTaskGroups, and Foundation rewrite.
One of the side effects of Elon Musk taking over Twitter was that the provider hosting this very newsletter is being shut down as soon as this month. So, while I'm still traveling Japan (sadly, only a few days left 😔), I had to find a new provider and migrate everything over, which is why this issue comes unusually late. And that's also why the design and sender of the newsletter will look different if you're reading this as an email newsletter.
Here's the new link for the newsletter: https://swiftevolution.substack.com
If you prefer an RSS reader, use this: https://swiftevolution.substack.com/feed
I skipped the November issue on purpose, as there weren't enough interesting new proposals to cover from the perspective of app developers. But December had some interesting ones incoming, and there were even two proposals posted in January, which I will summarize in the next issue. I also decided to move one more proposal summary to the next issue, otherwise, this text would have been too long.
Without further ado, let's get on with what happened in Nov/Dec!
Accepted Proposals
The following proposal already presented in the past has been accepted:
Proposals In Review/Revision/Waiting
You can still provide feedback for these proposals. The current rejection rate is <10%, so they're likely to get accepted. Revisions are more common.
SE-0378: Package Registry Authentication
Links: Proposal Document 📝 | Review 🧵
Before we get to the news of this proposal, let me first clarify what a "package registry" is, as this newsletter was started after they were accepted to Swift and thus I didn’t cover them yet. SE-0292: Package Registry Service, which was only recently shipped with Swift 5.7 (in Xcode 14), adds an alternative way of declaring package dependencies when using SwiftPM. Instead of using Git and its actions like clone
or checkout
for fetching dependencies by SwiftPM, the idea of a package registry is to define a RESTful API as an alternative to fetching via Git. A true API like provided by a package registry has performance and reliability advantages compared to using Git for dependency resolution and fetching. The good news is, from an app or framework developers perspective, these details are hidden by SwiftPM and the registry services that implement them. Unless you plan on implementing such a registry service yourself (which might make sense for larger companies, please read the full proposals in that case!), all you need to know is this:
Thanks to SE-0292, starting with Swift 5.7 whenever you add a dependency to your projects using SwiftPM, you can decide to either provide the GitHub URL (which was always possible) or alternatively provide a sort of identifier for the project consisting of a scope
and the name
of the package, e.g. something like FlineDev/HandySwift
. In this example, FlineDev
is my personal GitHub organization name which serves as the scope and HandySwift
(a lib where I extract my reusable Swift code to) uniquely identifies a convenience library within that scope. Now, SwiftPM will search package registries you’re signed in to in order to find and download a version of my HandySwift
library.
Yes, you read that right: You need to first be signed in to a package registry. Also, of course the library needs to be available through that registry. Apple announced that they are actually aiming to (effectively) replace the currently Git-based dependency management with a registry-based one long term. They will cooperate with the already useful Swift Package Index project (a website that helps discover packages, hosts documentation & more), which Dave Werver, one of the projects main contributors, confirmed in his newsletter. This is likely to become a one-stop place for all of Swift developers to get Open Source libraries from which (I can imagine) could even become the default server built into SwiftPM at some point if adoption is high, similar to how things work with CocoaPods already (or RubyGems for Ruby / PyPI for Python / npm for JavaScript). But we’ll see how things will turn out.
Now, the new thing this proposal introduces is another authentication method: Currently, only basic authentication is supported, which limits some interactions. This proposal aims to add token authentication and fixes some security aspects of the current authentication system (no project-level auth file & use Keychain on macOS). I’m happy to hear that we are finally getting closer to having a faster and more reliable Swift dependency management system.
SE-0379: Swift Opt-In Reflection Metadata
Links: Proposal Document 📝 | Review 🧵
Are you making use of reflection APIs in Swift in your apps today? As a reminder: Reflection is the act of building your logic upon the structure of your (or someone else’s) Swift code. For example, for a type Person
you could use the Mirror
type and pass it an object of type Person
to get information about the properties and their names to act accordingly. You might think that this seems useful in some rare scenarios, but I’m sure you’ve used reflection APIs already. If you’ve done any kind of debugging, chances are that you might have used one of the print
, debugPrint
or dump
methods and passed them an object already. Guess what, the reason these give you some useful information is that they are making use of Reflection under the hoods!
But independent of if you’re planning on making use of reflection APIs explicitly sometime in the future or not, this proposal will profit anyone: Because it introduces fine-grained control over when reflection on a type is needed and when it is not and provides compile-time feedback. Basically, up until now, all code had to be compiled in a way that made reflection possible by adding metadata to your app binary, such as the property names firstName
and lastName
for a type like Person
– just in case some library or SDK you were using relied on reflection like SwiftUI does for tracking changes in your model. Of course, this means that it is much easier to reverse-engineer our compiled code back to its original code form with these kinds of metadata than it would be if the name of the property wouldn’t be clear. Also, it is currently possible to completely turn off reflection metadata generation without any compiler warnings, which can lead to unexpected behavior.
This proposal aims to solve that by introducing a new Reflectable
protocol to mark types that should support reflection metadata explicitly, so only for those types full metadata is provided. A migration path is provided so existing code doesn’t break immediately. Instead, Swift users will (probably) get a warning at first, so they can migrate their types over to Reflectable
. Only once Swift 6 is out, the new behavior will be turned on by default (use the flag -enable-full-reflection-metadata
to turn it on before that). For those who want to use reflection explicitly in their own code, they’ll be able to specify that their functions only work on types that conform to Reflectable
and this way can be sure they can reflect on the provided data. And for those who just want to use APIs that use reflection under the hood, they will get an error at compile-time if they pass in data that doesn’t conform to Reflectable
, making Swift reflection code safer, more reliable, and harder to reverse-engineer all at the same time.
SE-0380: `if` and `switch` expressions
Links: Proposal Document 📝 | Review 🧵
Consider this sample code showing a portion of logic for a Chess game app, read especially the comments I've added showing different implementation patterns:
class ChessGame {
enum Direction: CaseIterable {
case up, right, down, left, upRight, downRight, downLeft, upLeft
}
enum Piece {
case pawn, rook, knight, bishop, queen, king
// implemented as if-else with multiple returns
var maxFieldsCanMoveInOneTurn: Int {
if self == .pawn || self == .king {
return 1
} else if self == .knight {
return 3
} else {
return 7
}
}
}
func movementLogic(of piece: Piece) {
// implemented as switch-case with multiple returns in a self-executing closure
let movableDirectionsForWhitePlayer: [Direction] = {
switch piece {
case .pawn: return [.up]
case .rook: return [.up, .right, .down, .left]
case .bishop: return [.upRight, .downRight, .downLeft, .upLeft]
case .knight, .queen, .king: return Direction.allCases
}
}()
// implemented as switch-case with Swift's definite initialization feature
let movableDirectionsForBlackPlayer: [Direction]
switch piece {
case .pawn:
movableDirectionsForBlackPlayer = [.down]
case .rook:
movableDirectionsForBlackPlayer = [.up, .right, .down, .left]
case .bishop:
movableDirectionsForBlackPlayer = [.upRight, .downRight, .downLeft, .upLeft]
case .knight, .queen, .king:
movableDirectionsForBlackPlayer = Direction.allCases
}
// …
}
}
Of course, this code could be refactored in multiple ways, but let’s stick to this code example for educational purposes. There are cases when some developers (including me) actually want this kind of in-line structure, especially if we want to return different but simple and clear values for different cases in code. Well, I have great news for you, because this proposal is doing exactly that and is simplifying the above code in many ways:
First, we no longer need to state the return
keyword in if
or switch
statements if (and only if) all branches have exactly one statement and when the type of this statement is the same for all of them (independently!). Second, we no longer have to write a self-executing closure in these cases anymore, we can directly assign to variables from a if
or switch
statement. The latter aspect actually is what differentiates a mere statement
from an expression
: An expression always evaluates to a value, where an if
or switch
statement is only a structure for conditional code, a statement doesn’t (necessarily) evaluate to a value. But if
and switch
now will be usable as expressions when returning from functions/properties/closures or when assigning to variables.
With this proposal, the above code can be written as:
class ChessGame {
enum Direction: CaseIterable {
case up, right, down, left, upRight, downRight, downLeft, upLeft
}
enum Piece {
case pawn, rook, knight, bishop, queen, king
// no need for any return keyword
var maxFieldsCanMoveInOneTurn: Int {
if self == .pawn || self == .king { 1 }
else if self == .knight { 3 } else { 7 }
}
}
func movementLogic(of piece: Piece) {
// no return keyword, no need for self-executing closure
let movableDirectionsForWhitePlayer: [Direction] =
switch piece {
case .pawn: [.up]
case .rook: [.up, .right, .down, .left]
case .bishop: [.upRight, .downRight, .downLeft, .upLeft]
case .knight, .queen, .king: Direction.allCases
}
// no return keyword, no need for definite initialization
let movableDirectionsForBlackPlayer: [Direction] =
switch piece {
case .pawn: [.down]
case .rook: [.up, .right, .down, .left]
case .bishop: [.upRight, .downRight, .downLeft, .upLeft]
case .knight, .queen, .king: Direction.allCases
}
// …
}
}
I highlighted the differences in the comments. Isn’t this much cleaner? I personally use self-executing closures from time to time, so now being able to get rid of them alongside the return keyword is really making the code read more naturally to me. Today I prefer to put each return in a switch-case
in its own line plus an empty line before the next case for best readability, causing them to become really bloated (see here for example). I’ll for sure consider returning in one line like this in the future!
SE-0381: DiscardingTaskGroups
Links: Proposal Document 📝 | Review 🧵
This one is an improvement to Structured Concurrency in Swift, primarily aimed at improving memory management on the server. If you are new to Swifts' new concurrency mechanisms such as async
/await
or with(Throwing)TaskGroup
, you might want to read up on the two most relevant proposals' "Proposed Solution" sections as I can’t summarize them in short here and they are pretty well written: SE-0296 Async/await and SE-0304 Structured concurrency.
In short, this proposal introduces two new TaskGroup
variants (we currently have TaskGroup
and ThrowingTaskGroup
) which do not work like a sequence and thus they don’t conform to AsyncSequence
(unlike TaskGroup
/ThrowingTaskGroup
). Instead, they automatically clean up any child Task
s that complete, and they could be potentially made to conform to Sendable
, which is planned for a future proposal. The new types DiscardingTaskGroup
and ThrowingDiscardingTaskGroup
are provided as a parameter to the new with(Throwing)DiscardingTaskGroup
global function. Other than the already mentioned differences, they work exactly like the previous task group types (and their related global function with(Throwing)TaskGroup
).
The automatic child task clean-up nature makes these new types suitable for any situation where events are consumed for a longer period of time while creating (potentially long-running) tasks. The perfect use case for this is an HTTP or RPC server that handles an indefinite amount of requests. It's not possible to write memory-efficient concurrency code for these kinds of scenarios right now.
It’s impressive to see how Swift on the Server is moving forward steadily over such a long period of time already, and there are still so many more things planned!
Recently Active Pitches/Discussions
Some threads inside the “Evolution” category with activity within the last 2 months I didn’t link yet. I’ll cover them in detail once (and if) they become proposals:
- Observation
- Reflection
- Single Quoted Character Literals
- New Access Modifier: package
- Inout variables in for-in loops
- Custom Metadata Attributes
- `contains(_:)` for Ranges
- Cross-Compilation Destination Bundles
- Result builder scoped unqualified lookup
- Allow Property Wrappers on Let Declarations
- Don’t copy `self` when calling non-mutating methods
Other Developments worth Mentioning
I noticed that a new process seems to have emerged in Swift Evolution which I would call "Vision-Driven Development". We've always had manifestos in the Swift open-source project, like the StringManifesto or the recently very relevant GenericsManifesto and OwnershipManifesto. Each of these claimed to be a vision for their topic. I've also seen sometimes that the first proposal of a related set of proposals was partly used as some kind of vision document, like SE-0350 Regex Type and Overview.
But as the Swift code base grows, the Swift Evolution process evolves and so we got a formalization of these kinds of vision documents. Basically, the proposals
directory in the Swift Evolution GitHub repo has been joined by a visions
directory. And all vision documents will go through a lightweight (internal) review process before getting officially "accepted" and therefore will be even more reliable than before, where the process of the creation of these documents wasn't clear.
The first vision already accepted is "Using Swift from C++" (see discussion). Two more prospective vision documents have been posted, one about Variadic Generics (discuss here) and one about Macros (discuss there). This kind of formalization is great to see, as it further streamlines the Evolution process.
Apart from that, there has been some exciting news from Apple regarding Swift: Firstly, the Swift projects focus areas for 2023 were announced on the Swift blog which is well worth a read (related discussion on Swift Forums). I just want to highlight two things that could be easily overlooked: The Documentation Workgroup will include guidelines for writing great documentation into the newly open-sourced "The Swift Programming Language" book, which I really appreciate. And the Language Workgroup will publish detailed documentation including guidelines for proposal authors and reviewers, which I think will help get more developers involved in writing or reviewing Evolution proposals in the future. But there's much more in the article!
Secondly, Apple has announced that they are open-sourcing Foundation! Okay, not the Foundation code we are using today (which probably has lots of old Obj-C and even wrapped C code anyways), but they are doing the closest thing possible, which is an open-source rewrite of Foundation in Swift which will replace Foundation on Apple platforms in the future. Swift on Server environments will finally get access to all of the same Foundation code.
They will use this opportunity to properly group things into smaller packages, FoundationEssentials
becoming an even slimmer version of today's Foundation. This way you can skip FoundationInternationalization
for non-localized projects or things like XML or networking support where you don't need them. Also, thanks to a new contribution process, you will not only be able to dig into code if you run into bugs and fix them yourself, but you will also be able to contribute entirely new APIs to Foundation.
The year 2023 really looks like an exciting year for Swift developers. I'm hyped!
A native Mac app that integrates with Xcode to help translate your app.
Get it now to save time during development & make localization easy.