Swift Evolution Monthly: First Issue
Summary of interesting developments on Swift Evolution - Update from 03/2022 + some background history (1st issue!)
Recap of Swift Evolution History
Since the day Apple open-sourced Swift in December 2015 it was possible to follow and even participate in the future of Swift. At the beginning the discussions were hard to explore & a mess to follow due to their mailing list nature. Thankfully, more than a year after the first suggestion to migrate over to a proper forum software and dozens of participators backing the idea, the Core Team had decided to do the migration. It took nearly another year until the Swift Forums officially opened in January 2018.
In the 4 years since then ~17k developers discussed Swift topics in ~27k threads with ~245k posts overall as of March 2022 (source). Even more developers (probably ~100k) have at least viewed and read some portion of them. A whole 9 new Swift versions were released since migrating to the forum, each accompanied by an official announcement blog article which lists all Swift Evolution proposals that went through the Swift Evolution Process and informed the changes: 4.1, 4.2, 5.0, 5.1, 5.2, 5.3, 5.4, 5.5, 5.6.
The road to Swift 6 is outlined since January 2020 and is the most-viewed thread in the forum for a reason. It summarizes the Swift 6 focus in 3 areas:
First, “the Swift software ecosystem” to broaden Swifts usefulness outside of Apple app development. Second, “a fantastic development experience” like faster builds & more accurate error messages. And third, “user-empowering language directions” such as improvements to concurrency, generics, DSLs, memory-ownership and low-level programming. All these areas have seen large steps forward since the announcement with the changes in Swift 5.2 and later, Swift Concurrency being a large recent focus. But there’s still much work to be done in all of them, so Swift 6 release is not a topic for this year and we can expect at least Swift 5.7 and 5.8 before that.
Why a “Swift Evolution Monthly”?
My main takeaway from the recap of the Swift Evolution history above is that the future development of Swift is truly a community effort. While Apple is still the main driver of this language (more on that at the end), setting the rough direction and current focus, we all have a say in all the details and can even make suggestions for improvements or entirely new features ourselves.
At the same time, we developers are “users” of the Swift language first and foremost. Being a “designer” of a language is an entirely different thing and can eat quite a large chunk of our precious time. Nevertheless, I’m personally very interested in what’s coming next in Swift so I can make informed decisions about which APIs or frameworks I make use of right now. And on which parts I maybe use a temporary solution and wait a couple of months before diving into making things proper. Because there might be a related Swift feature coming soon that will simplify things or allows entirely new concepts. But how would I know if I didn’t follow the discussions? And will the change actually work for my problem, too, or should I give feedback?
For these reasons, I sometimes wander around in the Pitches category to find ideas I want to support. But more importantly, I’m skimming through the Proposals that are officially in review or even accepted. Of course, I’m reading Paul Hudson great write-ups whenever a new Swift version is released, but for an informed long-term decision-making that’s too late for me.
Actually, Paul Hudson used to talk about new and coming Swift features together with Erica Sadun in the Swift over Coffee podcast. Likewise, Jesse Squires and JP Simard used to talk about Swift Evolution proposals in their Swift Unwrapped podcast. But both stopped recording new episodes after their WWDC episodes in 2020 and 2021, respectively.
The same Jesse Squires had also started the Swift Weekly Brief newsletter in 2015 right after Swift was open-sourced, to stay up-to-date with the latest in Swift Evolution. The community-driven newsletter was later maintained by Bas Broek and most recently by Kristaps Krinbergs. But unfortunately, it ended a few months ago with Issue #200 on December, 16th 2021.
So, here we are, me trying to fill the gap with a monthly summary of what interesting developments I’ve come across in the Swift forums.
Accepted Proposals
SE-0329: Clock, Instant, and Duration
Problem:
We have many different types to measure & do calculations with continuous time: Date
, TimeInterval
, DispatchTime
, DispatchTimeInterval
, …
Currently, there’s no unified concept of time in all frameworks. Also, TimeInterval
is technically just a typealias
for Double
and is implicitly respresenting time in the unit of “seconds”. There’s no explicitness to the unit, nor are there any APIs available to convert easily between units (like here). Also, as a floating-type, TimeInterval
is susceptible to rounding errors.
Solution:
Three new types will be introduced to unify the concepts of time in Swift:
public struct Duration {
public var components: (seconds: Int64, attoseconds: Int64)
}
Duration
prevents any inexact rounding errors like with Double
by storing the time interval in two Int64
variables: seconds
and attoseconds
. But this internal representation will probably never be accessed directly, as many convenience extensions are provided, such as a static .seconds
method to write readable code like let duration: Duration = .seconds(5)
. Duration
also supports arithmetic operations such as +
, —
or *
and /
.
public protocol InstantProtocol {
func advanced(by duration: Duration) -> Self
func duration(to other: Self) -> Duration
}
Types adhering to the InstantProtocol
, such as Date
in the future, basically represent an instant in time and have APIs to interact with the Duration
type. Date
s addingTimeInterval()
method gets unified to advanced(by:)
and timeIntervalSince()
method gets unified to duration(to:)
. Types adhering to InstantProtocol
will also support the arithmetic operations +
and -
.
public protocol Clock {
associatedtype Instant: InstantProtocol
var now: Instant { get }
func sleep(until deadline: Instant, tolerance: Instant.Duration?) async throws
}
extension Clock {
func measure(_ work: () async throws -> Void) reasync rethrows -> Instant.Duration
}
Lastly, types adhering to the Clock
protocol, like a new UTCClock
type in Foundation in the future, will provide a way to get the current time instant via a static .now
computed property (so UTCClock.now
will be equal to Date()
). Also, they will provide an asynchronous sleep(until:tolerance:)
function. An extra convenience function measure(work:) -> Duration
will help stopping the time of executing a chunk of code — neat! The Clock
protocol will also make it easier to create a custom type for controlling time in tests.
Expected in: ~Swift 5.7 (my guess based on merged PR from Feb 16th)
SE-0339: Module Aliasing For Disambiguation
Problem:
In SwiftPM, when including two dependencies that both had a target named Utils
for example, currently we would get a name collision error. To solve this, we would need to edit one of the dependencies (e.g. by forking) and rename the Utils
target in one to something else, like GameUtils
.
Solution:
Instead of editing the dependency, we will be able to just provide an alias name to a specific packages dependency right in our projects Package.swift
manifest file by using the new moduleAliases
parameter like so:
.target(
name: "App",
dependencies: [
.product(name: "Game", moduleAliases: ["Utils": "GameUtils"], package: "swift-game"),
.product(name: "Utils", package: "swift-draw"),
]
)
Expected in: ~Swift 5.7 (my guess based on merged PR from Feb 9th)
SE-0341: Opaque Parameter Declarations
Problem:
Today, we can just return some View
from functions instead of having to specify a generic type <T: View>
. This kind of return type with the some
keyword is called an “opaque type” (see SE-0244). It saves us boilerplate code. If you’re like me, you might have tried to specify some View
in parameters of functions as well, when trying to extract some SwiftUI code from a body
that got too long. But it doesn’t currently compile…
Solution:
In the future it will compile! For example, instead of writing this:
func horizontal<V1: View, V2: View>(_ v1: V1, _ v2: V2) -> some View {
HStack {
v1
v2
}
}
You’ll be able to write this (note that no generic types are needed):
func horizontal(_ v1: some View, _ v2: some View) -> some View {
HStack {
v1
v2
}
}
Expected in: Swift 5.7 (confirmed in ‘Status’ field)
Proposals In Review
SE-0344: Distributed Actor Runtime
Problem:
This is a large one — so large it even has a Table of Contents! — and I don’t fully understand it yet myself. But together with SE-0336 Distributed Actor Isolation, which was accepted back in January, it seems this is one of the more amazing and unique features Swift will provide compared to other modern languages.
The problem, as far as I understand, is that the newly introduced actor
type provides safety only on a localenvironment level, as in “safety for access between different threads on the same process of one machine”. (If you’re new to actor
s in general, Paul Hudson has a great write-up on that, too.) An actor
type does not provide safety in accessing data between different processes or even different machines though. This makes writing correct code accessing the same data across machines hard, for example in client-server situations or in live peer-to-peer communication.
Solution:
Besides the new distributed actor
type and DistributedActor
protocol accepted in SE-0336, a new DistributedActorSystem
protocol is introduced here which basically defines details about how communication between different environments happens. If I understand it correctly, different implementations for different purposes can be provided, such as one for cross-process communication, one for clustering and another for client-server communication. In the end, we would be able to define a function as being distributed
much like we can now define functions to be async
like so:
distributed actor Game {
var state: GameState = ...
// players can be located on different nodes
var players: Set<Player> = []
distributed func playerJoined(_ player: Player) {
others.append(player)
if others.count >= 2 { // we need 2 other players to start a game
Task { try await self.start() }
}
}
func start() async throws { ... }
}
Then all calls into such functions need to be await
ed for like here:
await game.playerJoined(newPlayer)
So the overall goal of this is to make distributed environment access to data (nearly) as simple as thread-safe access to data has become with actor
s. Does this hold the potential to even eliminate all our client-server networking code in our apps, provided that the server is implemented in Swift, too? I have no idea. And from what I hear, that’s not the main goal here. But it sounds like a very interesting area to keep an eye on in the coming years ahead for sure!
Expected in: Swift 5.8 or later (partially implemented, feature flag on main
)
SE-0345: if let
shorthand for shadowing an existing optional variable
Problem:
Optional binding as in if let foo = foo { … }
is extremely common in Swift code. Repeating the same identifier twice is verbose and can lead to weird indentation situations when line limits are reached with long variable names. Solving this verbosity was already requested back in 2015 by the community, shortly after Swift had been open sourced.
Solution:
Instead of repeating the variable name like in this code:
let someLengthyVariableName: Foo? = ...
let anotherImportantVariable: Bar? = ...
if let someLengthyVariableName = someLengthyVariableName, let anotherImportantVariable = anotherImportantVariable {
...
}
We would be able to get rid of the right = repeatingVariableName
part like so:
let someLengthyVariableName: Foo? = ...
let anotherImportantVariable: Bar? = ...
if let someLengthyVariableName, let anotherImportantVariable {
...
}
Expected in: ~Swift 5.7 (small PR 👍, but Review feedback is quite mixed)
SE-0346: Lightweight same-type requirements for primary associated types
Problem:
Have you ever written a function for a concrete type, like for Array
:
func concatenate(_ lhs: Array<String>, _ rhs: Array<String>) -> Array<String> {
...
}
And then later you wanted to generalize it over all types conforming to Sequence
to also make it work with Set
, Dictionary
and more like this:
func concatenate<S : Sequence>(_ lhs: S, _ rhs: S) -> S where S.Element == String {
...
}
You find it weird that we specify the where
clause at the end? And you find it cumbersome to lookup the name of the associatedtype
(here Element
) within the implementation of the Sequence
protocol? These and other annoyances around generics exist at the moment, see also this forum thread.
Solution:
Consistent to how we write generic type requirements for concrete types like Array<String>
, we would be able to specify the associated type right in-place:
func concatenate<S : Sequence<String>>(_ lhs: S, _ rhs: S) -> S {
...
}
We could emit the where
in more places, all these pairs would be equivalent:
// Extensions
extension Collection where Element == String { ... }
extension Collection<String> { ... }
// Inheritance
protocol TextBuffer : Collection where Element == String { ... }
protocol TextBuffer : Collection<String> { ... }
// Nested Opaque Parameters
func sort<C : Collection, E : Equatable>(elements: inout C) {} where C.Element == E
func sort(elements: inout some Collection<some Equatable>) {}
Expected in: Swift 5.7 or later (partially implemented, feature flag on main
)
Other Developments worth Mentioning
Did you know that Chris Lattner, the original author of Swift, has decided to leave the Swift core team and even the Swift Evolution community? If you haven’t come across this news already, I recommend reading his detailed post on the reasons why he’s leaving, which is a unique chance to get some detailed insights in how Swift has been developed in the past.
Just a few chosen (negative) quotes from his post:
… the core team is a toxic environment in the meetings themselves.
… after being insulted and yelled at over WebEx (not for the first time, and not just one core team member) …
… a complicated situation and many pressures (including lofty goals, fixed schedules, deep bug queues to clear, internal folks that want to review/design things before the public has access to them, …)
But he does end on a positive note, which I totally agree with:
I think that Swift is a phenomenal language and has a long and successful future ahead …
I think that a healthy and inclusive community will continue to benefit the design and evolution of Swift.
I personally am very thankful for all Chris has done for our community and wish him all the best for his future! It’s worth mentioning that there were some deeper structural changes to the Swift core team announced already that might help improve the situation. As for the struggles he seemed to have had, it’s hard to comment on them without further details.
Knowing nothing about Apples internal plans and the discussions of the core team, I am still optimistic about the community aspect of Swift in the years to come. While Apple is still far away from being a transparent company, from my experience they are trying to open up more and more recently on developer topics. For example, they open sourced DocC last year at WWDC. They also open sourced the Markdown parser they use in SwiftUI. And just this week, they even open sourced the swift.org website itself. So things are going in the right direction. Sometimes it might feel like two steps forward, one step back. But even that is still a step forward overall!
I’m looking forward to what else the future holds for us Swift developers. A lot is going on for sure. And I hope that this article helped you explore some of it. Follow me if you don’t want to miss future Swift Evolution Monthly posts.
A native Mac app that integrates with Xcode to help translate your app.
Get it now to save time during development & make localization easy.