Swift Evolution Monthly Jan + Feb '23

Expression Macros, Deprecate @UIApplicationMain, Forward Declared ObjC Interfaces, Swift 5.8 & 6 schedule

Swift Evolution Monthly Jan + Feb '23
Photo by dan carlson / Unsplash

The last issue had an unusual delay already because I had to move the newsletter over from one service to another while I was on travel in Japan. This time around I had to skip an entire month because I had to move my flat from one city to another right after, which was a much much bigger task! 😱📦💪

In fact, I couldn't work at all for about 5 weeks and the relocation is still not fully completed. But that's how life is sometimes. At least I have a full workroom to myself now (instead of just a table in my bedroom), which could help to be more productive in the future. Also, I will finally have a real background in my Livetreams instead of having to use a green screen like before (to hide my bed 😂).

Additionally, I've decided to change the structure of this newsletter a bit. I noticed that some proposals I summarized went through (sometimes significant) changes during the review process, such as the move operator which was first renamed to take and then later to consume. It's hard for me to keep up with these changes and edit my summaries (in time), plus edits aren't even possible for sent emails.

So, starting with this issue, I will be summarizing only the accepted proposals. I'll treat other proposals like I treated pitches/discussions before and list them in their own section with their title and related links so you can read up on what sounds interesting. The related links will also include the vision document from now on (if one exists), so you can read up on the bigger picture.

Also, I've created a new account for this newsletter on both Twitter & Mastodon where I plan on posting even shorter summaries of proposals as soon as I've worked through them. I might also send out other Evolution-related posts from time to time, so if you're interested in more frequent news, make sure to follow: @SwiftEvolutionM (Twitter) / @SwiftEvolutionM@iosdev.space (Mastodon)

With that said, let's get to the summaries of all proposals accepted in Jan./Feb.:

SE-0382: Expression Macros

Links: 📝 Proposal | 💬 Reviews: 1st, 2nd  |  ✅ Acceptance  |  🔮 Vision

This new feature will introduce a new kind of function-like structure called an "Expression Macro" which will come in very handy to write more expressive libraries and eliminate boilerplate in code. These new macros work very similarly to functions because they take data through arguments, do something with those data and return some new data. But the big difference is that their execution happens during compile time, which means that the data they operate on is not runtime user data, but it's the source code of your app (or library)!

Yes, you heard that right, it's like a little hook we get into the compilation process so we can eliminate having to repeat writing similar code in various places to make things in our code consistent. Because you have access to your source code in macros, they will even allow for new concepts currently impossible to write in Swift. Have you ever used #filePath,  #function, #line, #selector, #colorLiteral, #imageLiteral or other things starting with # in your code? Well, those are all built-in expressions that will be subsumed into macros and ship as macros in the Swift standard library. These should already give you an idea of what kind of features are possible with macros. Here are 9 more use cases for macros:

  1. Domain-specific languages (DSLs) – e.g. mathematics, database queries
  2. Code generation – e.g. JSON to Codable struct
  3. Debugging – e.g. print value of variable before & after a function call
  4. Performance optimization – replace runtime checks with compile-time checks
  5. Syntax extensions – e.g. new control flow constructs or data types
  6. Functional programming – e.g. simulate curried functions
  7. Code instrumentation – e.g. measure performance, monitor usage
  8. Algorithmic complexity analysis – e.g. determine time complexity of sorting
  9. Code obfuscation – e.g. for hard-coded Strings like API keys

It's hard to tell if this proposal alone already enables all these use cases until we try this new feature out, for example, because the implementation will be sandboxed like with SwiftPM plugins. But at least it's a big first step toward the larger vision. While this sounds quite exciting, writing a macro has its own learning curve as you'll have to make use of the swift-syntax library and a new SwiftSyntaxMacros module that will provide what's required to define macros.

I won't go into all details of how to implement a macro, but it's worth noting that there will be two parts to it: The declaration and the implementation. Much like with functions, where func description(count: Int) -> String is the declaration and the code within { ... } is the implementation. But with macros, those two are not necessarily within the same file and can/must be written separately:

import SwiftSyntax
import SwiftSyntaxBuilder
import _SwiftSyntaxMacros

// Implementation:
public struct StringifyMacro: ExpressionMacro {
  public static func expansion(
    of node: some FreestandingMacroExpansionSyntax,
    in context: some MacroExpansionContext
  ) -> ExprSyntax {
    guard let argument = node.argumentList.first?.expression else {
      fatalError("compiler bug: the macro does not have any arguments")

    return "(\(argument), \(literal: argument.description))"

// Declaration:
macro stringify<T>(_: T) -> (T, String) =
  #externalMacro(module: "ExampleMacros", type: "StringifyMacro")

// Usage:
let (a, b): (Double, String) = #stringify(1 + 2)
Sample macro from the proposal. Another macro #externalMacro ties things together.

Doug Gregor from the Language Workgroup is maintaining a GitHub repo with many more sample implementations of macros if you're interested.

I'm really looking forward to what new expression macros Apple will introduce during WWDC this year, and I'm also sure the community will come up with some creative uses to simplify app coding life, make code safer or streamline the API of their libraries.

SE-0383: Deprecate @UIApplicationMain and @NSApplicationMain

Links: 📝 Proposal | 💬 Review  |  ✅ Acceptance

The 'Introduction' section of this proposal summarizes this pretty well:

@UIApplicationMain and @NSApplicationMain used to be the standard way for iOS and macOS apps respectively to declare a synthesized platform-specific entrypoint for an app. These functions have since been obsoleted by SE-0281's introduction of the @main attribute, and they now represent a confusing bit of duplication in the language. This proposal seeks to deprecate these alternative entrypoint attributes in favor of @main in pre-Swift 6, and it makes their use in Swift 6 a hard error.

There's not much more to say about it. But you could use this as a reminder to switch from @UIApplicationMain to @main today to prevent the warning and be Swift-6 conformant. Or wait for the fix-it to appear in a future Xcode release.

SE-0384: Importing Forward Declared Objective-C Interfaces and Protocols

Links: 📝 Proposal | 💬 Review  |  ✅ Acceptance

This concerns everyone calling code written in Objective-C from within Swift (like when using older libraries). The idea is to port a common workaround for cyclic dependencies in Objective-C code to Swift in an automatically synthesized way:

In Objective-C, the public API of a type is declared in a header .h file. When a type named User wants to declare a public property credentials which has its own type Credentials, it needs to import its header file  Credentials.h. Now, when the Credentials type had a property named user of type User, it would need to import the header file User.h which is a cycling dependency that is not allowed and causes a compiler error. To fix this, there's a workaround in Objective-C where one declares an empty type to specify that a specific type "exists" without actually importing its header, this technique is called "forward-declaration".

To use such Objective-C APIs in Swift, one had to "forward-declare" those types in Swift explicitly, which is very inconvenient. In the future, that won't be necessary. Swift will automatically synthesize forward-declared types in Swift as follows:

// @class Foo turns into
@available(*, unavailable, message: “This Objective-C class has only been forward declared; import its owning module to use it”)
class Foo : NSObject {}

// @protocol Bar turns into
@available(*, unavailable, message: “This Objective-C protocol has only been forward declared; import its owning module to use it”)
protocol Bar : NSObjectProtocol {}

This new behavior will be turned on by default in Swift 6, but needs to be opted-in with Swift 5.x by using the flag -enable-import-objc-forward-declarations.

To receive future issues via email, subscribe to the newsletter here.

Proposals in Progress

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:

Want to see your ad here? Contact me at ads@fline.dev to get in touch.

Other Developments worth Mentioning

I've already presented several features that will find their way into one of the next Swift versions (e.g. 5.8) but that are opt-in for Swift 5.x and will only be turned on by default in Swift 6, like the last proposal summarized above. In this thread, the Language Workgroup has now clarified the 3 focus areas for all breaking changes planned for Swift 6: Data-race safety by default, performance predictability, and package ecosystem scalability. Read the full post for more details.

Further down in the thread, asked for a rough idea of when we can expect Swift 6, Doug Gregor from the Language Workgroup clarifies that Swift 6 definitely won't be released within 2023. So there's still some time until we can expect these opt-in features to be turned on by default.

That's why I'd like to remind everyone to consider opting into these potentially source-breaking opt-in features as soon as Swift 5.8 is released, which should happen within the next 30 days if history repeats. Like Doug says in the post, by doing so: "Developers will reap the benefits from these improvements sooner, rather than wait until the Swift 6 language version is available and complete." Which will take a while. See my summary of SE-0362 for how to opt in.

And that's it for this issue. Til next time!

Enjoyed this article? Check out my app RemafoX!
A native Mac app that integrates with Xcode to help translate your app.
Get it now to save time during development & make localization easy.
Want to Connect?
Follow me on 👾 Twitch, 🎬 YouTube, 🐦 Twitter, and 🦣 Mastodon.