Swift Evolution Monthly: July '22

My first Indie App, my first Evolution pitch experience, the last of 6 Regex proposals, external conformance warning, implicit [weak self] capture & a new 'move' keyword.

Swift Evolution Monthly: July '22
Photo by Michal Balog / Unsplash

Here we are again with another monthly summary of Swift Evolution. July was a very special month for me, so I'd like to start with two topics that are important to me personally. I hope you'll excuse that – but both are still on-topic:

Firstly, my very first app as an Indie Developer, which I've been working on for six months now, is finally done and in Open Beta! It's an app for Swift developers and improves the localization workflow in Xcode. In the first version, among other things, you can add new localized keys to your projects in multiple languages without ever leaving your Swift file and safely reference them in code – just by filling out a single form! The app will take care of adding entries to all the right files and even optionally machine-translate to multiple languages, see this GIF:

A GIF showing how you can add a new key to your app with ReMafoX.

If this looks interesting to you, please try the beta and send me your feedback.

Secondly, one day after sending out the previous issue of this newsletter, I wrote my very first own Swift Evolution pitch. In the thread, I'm requesting a new syntax to improve the usage of the Result type as an alternative for Swift's native error handling. In the above-mentioned app project, I preferred to have typed errors in order to vastly improve my error handling experience by forcing me to handle all possible error cases with a useful message. But Swift currently doesn't support typed throws, so I used Result instead and migrated all my throwing functions in the above-mentioned app to it. I'll be writing about my approach in detail soon on my dev blog if you're interested. But I came across two safety & convenience issues that I couldn't fix without changes in the language itself. Thus, the pitch.

If my pitch sounds like something you'd want to have in Swift, don't get too excited though. Even though I tried to scope it clearly and I tried to make the change as small and language-fitting as possible, I quickly received a lot of refusing comments due to historical discussions on typed throws, even though my pitch is just about adding convenience & safety to Result – to improve things already possible in Swift today. In fact, the topic of typed throws seems to be one of the most controversial topics on Swift Evolution, which I learned the hard way in this thread. This was not the great first-pitch experience on Swift Evolution that I was hoping for to write about in this issue of the newsletter.

Thankfully, after explaining my specific use case and clarifying that my intention was not to rehash any historical discussions, the topic of the thread changed from a judging "there's no problem in the language, you're just using it wrong" to a more accepting "let's explore how we can improve the valid use case you described" kind of vibe. I hope it stays like that and even more, I wish it was like that from the start. It's hard for me to motivate anyone in the developer community to be more active in the forums with their own pitches if I have to add to that that they should be cautious not to run into some of the difficult topics. But here we are.

I'm sure, the whole thing was a misunderstanding and I probably didn't communicate things clearly enough on my end. But in any case, this is a good opportunity to remind everyone (including me!) to always initially expect others to have good intentions and to respect the decisions they made for themselves. This is much better than expecting the worst and judging their decisions by making assumptions about their situation based on your own past experiences. It might be tempting to do that sometimes as it can simplify your life, but it's not always right and can even hurt people. After all, people are different, situations are different, therefore solutions will always be different, too. A "general purpose language" like Swift and the community around it should accommodate that.

With this longer comment this time, let's get on to this month's proposals!

Accepted Proposals

The following proposals already presented in the past have been accepted:

Proposals In Review/Awaiting Decision

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.

The following proposals already presented were returned for revision:

On to new proposals currently in review (or still awaiting a decision)!

SE-0363: Unicode for String Processing

Links: Proposal Document 📝 | Review 🧵

This is the last of the six Regex String-Processing proposals that were explored with the swift-experimental-string-processing package on GitHub before they were turned into a set of proposals. I covered them all in past issues (1, 2, 3, 4, 5) and they were all accepted already. Some of them have shipped as part of the Xcode 14 betas, it's worth mentioning this year's WWDC sessions Meet Swift Regex & Swift Regex: Beyond the basics again as they provide a great introduction to the topic.

This last proposal explains all the details of how Swifts Regex engine supports Unicode by default, which is not the case for many Regex engines. It also goes into the details of how character classes (which can be thought of as a Swift-native version of Foundations CharacterSet class) such as .digit, .whitespace and .wordCharacter are defined and how you can adjust them to fit your needs.

Here are a few key points worth pointing out:

  • Swift will do "grapheme cluster matching" by default in its Regex engine – this means that the Regex Caf. (the . matching "any" single character) will match Café always correctly – unlike e.g. C#, Rust, Go, Python, Objective-C, Java*, Ruby, and Perl which would just match Cafe without the acute accent on top of the "e" for an é that consists of multiple Unicode scalars ( "\u{65}\u{301}").
  • If you need "Unicode scalar" level matching semantics (e.g. for compatibility):
"Café".contains(/Café/.matchingSemantics(.unicodeScalar)) // => false
  • By default, the grouping of characters into character classes follows the modern Unicode Technical Standard #18. But custom matching behaviors can be provided, such as .ignoresCase(), dotMatchesNewlines(), anchorsMatchNewlines(), and asciiOnlyClasses(). Advanced Regex users might find defaultRepetitionBehavior() very handy.
  • The available character classes are: .any (= .), .anyNonNewline (= .), .anyGraphemeCluster (= \X), .digit (= \d), .hexDigit, .word (= \w),  .whitespace (= \s), .horizontalWhitespace (= \h), .verticalWhitespace (= \v), .newlineSequence (= \n or \R).
  • Character classes can be inverted, for example:
    .digit.inverted (= \D) and .word.inverted (= \W)
  • Like with Foundations CharacterSet class, you can build a union or intersection of two character classes or you can be  subtracting one from another and even build their symmetricDifference. You can also specify custom classes with the .anyOf and noneOf static methods. For example:
let floatingNumber: CharacterClass = .digit.union(.anyOf(".,"))

SE-0364: Warning for Retroactive Conformances of External Types

Links: Proposal Document 📝 | Review 🧵

Do you have code in your projects that extends a type from Apple or Swifts standard library to make them conform to the likes of Equatable, Identifiable, Hashable, or Codable? Did you know that it is discouraged to extend external types with custom conformances to these protocols? The reason is that in case Apple or the Swift community decides to add conformance to the type in the original framework, it's currently undefined which of the two implementations (yours or the frameworks) will be used, which can lead to unexpected behavior in your app. Note that this is only a problem for users who upgrade to a newer version of the OS but have a version of your app installed that you have not built with the new version of Xcode.

In my current project, I did a quick search on the left sidebar "Find -> Regular Expression" by pasting extension \w+: (Equatable|Identifiable|Hashable|Codable) and I found this extension, which I need because I use TCA which requires my view state types to be Equatable, and I need a Binding in some of them:

extension Binding: Equatable where Value: Equatable {
   public static func == (left: Binding<Value>, right: Binding<Value>) -> Bool {
      left.wrappedValue == right.wrappedValue
   }
}

This proposal suggests showing a warning in all places where you are extending an external type with these kinds of conformances. The idea is to make developers more aware of this potential issue and convince them to re-consider conforming an external type. To silence the warning you would add the module name in front of each involved type explicitly like so (note the SwiftUI. and Swift. prefixes):

extension SwiftUI.Binding: Swift.Equatable where Value: Swift.Equatable {
   public static func == (left: SwiftUI.Binding<Value>, right: SwiftUI.Binding<Value>) -> Bool {
      left.wrappedValue == right.wrappedValue
   }
}

SE-0365: Allow implicit self for weak self captures, after self is unwrapped

Links: Proposal Document 📝 | Review 🧵

Since Swift 5.3 it is possible to write a closure and capture [self] in order to not have to write self. in front of every call on properties and functions of the current type within the body of the closure (thanks to SE-0269) like this:

button.tapHandler = { [self] in
   dismiss()  // no need to write `self.dismiss()`
}

This proposal aims to introduce this same behavior for [weak self] as well when and only after the Optional of self is unwrapped. So for example a guard let self = self else { return } (or the newly possible guard let self { return }) would make self. calls for properties and functions unnecessary:

button.tapHandler = { [weak self] in
   guard let self { return }
   dismiss()  // no need to write `self.dismiss()`
}

SE-0366: Move Function + “Use After Move” Diagnostic

Links: Proposal Document 📝 | Review 🧵

This proposal adds a new move keyword that gives developers of performance-sensitive code more fine-grained control over when a variable or parameter should be released from memory. The compiler will then ensure a "moved" variable or parameter is no longer accidentally used unless a new value is reassigned to it explicitly. Most app developers won't ever need to use this keyword, but for those interested here's a function demonstrating the use of the move keyword:

func f(inout x: Int = 5) -> Int {
   print(x)  // => "5"
   
   let y = move x + 1
   print(x)  // error: 'x' used after being moved
   print(y)  // => "6"
   
   var z = move y
   print(y)  // error: 'y' used after being moved
   print(z)  // => "6"
   
   move z
   print(z)  // error: 'z' used after being moved
   
   z = 10
   print(z)  // => "10" (reassigned a new value to 'z', no error)
   
   x = z
   print(x)  // => "10" (reassigned a new value to 'x', no error)
   
   return z
}

Recently Active Pitches/Discussions

Some threads inside the “Evolution” category with activity within the last month I didn’t link yet. I’ll cover them in detail once (and if) they become proposals:

Other Developments worth Mentioning

As the introductory comment was long, I'm keeping this section short:

  • Some pitches seem to get discussed and "guided" proactively in a different direction by the new Language workgroup. To see how often this happened in the past I did a quick search for "The Language Workgroup / Core Team discussed this", revealing only this and that comment. I'll keep an eye on it.
  • A Documentation Workgroup, which I reported in the May issue as currently being discussed, has now been officially announced. The details are still in planning, but if you're interested "use the @swift-documentation-workgroup handle to reach out to the workgroup directly in forums".

That’s it from the July update!
(Sent a bit later this time because I'm currently on vacation. 🌴)

👤
Want to Connect?
Follow me also on 👾 Twitch, on 🎬 YouTube and on 🐦 Twitter.