What’s New in Swift 5.9? Explore Macros, Parameter Packs, and Ownership
Swift 5.9 introduces expressive macros, parameter packs, ownership‑based memory management, and limited C++ interop, while also allowing if and switch to be used as expressions, bringing powerful new tools to iOS developers.
Using if and switch as expressions
Prior to Swift 5.9, if and switch could only be statements; they could not produce a value to assign to a variable or pass as an argument. Swift 5.9 lifts this restriction, allowing these constructs to be used as expressions, which makes nested ternary operators unnecessary.
let bullet = if isRoot && (count == !willExpand) {
"n"
} else if count == g {
"- "
} else if maxDepth <= 9 {
""
} else {
"v"
}The new capability also works with optional binding:
let attributedName = if let displayName, !displayName.isEmpty {
AttributedString(markdown: displayName)
} else {
"Untitled"
}Macro system
Swift 5.9 adds a macro system that lets developers generate code using the Swift Syntax library. Two macro kinds exist:
Freestanding macros, prefixed with #, which appear independently of any declaration.
Attached macros, prefixed with @, which are attached to a declaration.
#aFreestandingMacro("with argument")
@AttachedMacro<T> struct AStruct {
...
}Several community‑driven macro tools are already available, such as swift-power-assert, swift-spyable, swift-macro-testing, and MetaCodable.
Parameter packs
Parameter packs remove the need to explicitly name every generic type a function handles, enabling a single generic definition to accept an arbitrary number of types. This is illustrated by SwiftUI’s ViewBuilder and the following example that checks whether all supplied arguments are non‑nil:
func all<each Wrapped>(_ optional: repeat (each Wrapped)?) -> (repeat each Wrapped)?
if let (int, double, string, bool) = all(optionalInt, optionalDouble, optionalString, optionalBool) {
print(int, double, string, bool)
} else {
print("got a nil")
}Ownership
Swift 5.9 introduces ownership concepts to fine‑tune memory management in performance‑critical code. The consume operator ends a variable’s lifetime, allowing the compiler to reclaim its storage, while borrowing lets a function use a value without taking ownership.
useX(x) // work with x
// Ends lifetime of x, y's lifetime begins.
let y = consume x // [1]
useY(y) // work with y
useX(x) // error: x’s lifetime ended at [1]
_ = consume y // [2]
useX(x) // error: x’s lifetime ended at [1]
useY(y) // error: y’s lifetime ended at [2]Additional modifiers newborrowing and consuming let developers explicitly declare ownership semantics for function parameters.
C++ interoperability
Swift 5.9 adds limited interoperability with C++. Only certain API types are supported. For example, given a C++ function:
#pragma once
#include <vector>
std::vector<std::string> generatePromptResponse(std::string prompt);It can be called from Swift as follows:
let codeLines = generatePromptResponse("Write Swift code that prints hello world")
.map(String.init)
.filter { !$0.isEmpty }
for line in codeLines {
print(line)
}Conclusion
The Swift team notes that C++ interop is still evolving based on real‑world feedback. Other notable additions include the ability to use if and switch as expression results, an improved debugger expression evaluator, faster p / po commands, and enhanced crash handling that shows backtraces directly in the console and supports generic‑type conditions in breakpoints.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
21CTO
21CTO (21CTO.com) offers developers community, training, and services, making it your go‑to learning and service platform.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
