Swift 5.9 adds library macros and explicit ownership control
Macros extend library expressiveness, noncopyable values model unique resources, and bidirectional C++ interoperability supports incremental integration.
Some repetitive Swift code describes a pattern that belongs to a library, yet the library previously had no way to generate the declarations that express it. Some resources belong to one owner, yet ordinary structs can be copied and classes permit shared ownership. Swift 5.9 adds mechanisms for both problems, while making their use explicit in source.
The September 18 release announcement introduces macros, parameter packs, new ownership facilities, and bidirectional C++ interoperability. The release broadens what library authors can express without requiring a separate language feature for each pattern. Its limits matter as much as its new syntax, since the ownership and interoperability work establishes foundations for further development.
I like Swift's local reasoning about values, especially the distinction between a value and a shared reference. The ownership additions address cases where copying is itself the wrong operation. That is a more precise extension of the model than asking every resource wrapper to become a reference-counted object.
Macros turn source patterns into library capabilities
Swift 5.9 has freestanding macros invoked with a hash-prefixed name and attached macros written as attributes on declarations. The syntax identifies expansion sites explicitly. A macro invocation is therefore distinguishable from an ordinary function call or property access.
The release announcement describes macro implementations as Swift functions using SwiftSyntax to generate code for insertion into a source file. Swift Package Manager supports macro targets through CompilerPluginSupport, so packages can distribute macros as library APIs.
SE-0389 on attached macros explains why attachments have roles. A peer macro can introduce declarations beside the attached declaration. Member macros can add members within a type, while accessor macros can provide property accessors. One macro can implement multiple roles.
Those roles constrain how generated code affects the surrounding program. A property-related macro might introduce backing storage through one role and accessors through another. The declaration remains the point around which those additions are organized.
This is different from allowing a macro to rewrite an arbitrary declaration into anything it chooses. SE-0389 discusses the costs of unrestricted mutation, including the difficulty of understanding a declaration before expansion. The role-based model gives tools and readers information about the kinds of additions to expect.
Generated names are part of the contract
An attached macro can introduce names that other code uses. SE-0389 therefore requires a declaration to describe the names it can produce. The proposal includes forms for overloaded names, explicitly named members, and names with a defined prefix.
That information helps users anticipate generated declarations. It also gives tools a way to avoid expanding every macro during unrelated name lookup. A macro that documents a narrow naming effect provides more information than a transformation that can introduce any name without qualification.
Expansion order also has a defined model. The proposal supplies each macro with the original syntax, without the additions made by another macro's expansion. This keeps independent expansions from silently changing one another's inputs.
The release announcement names Observation as an example of the capability enabled by macros. A class can acquire property-change notification behavior through the library's macro-based design. The larger point is that the library can adapt generated code to the declarations where the behavior is requested.
Macro support is initially available on macOS and Linux. The Swift 5.9 announcement says Windows support is coming, so cross-platform library authors need to distinguish the language capability from its initial host-platform coverage.
Ownership separates unique values from shared objects
SE-0390 starts from a resource-modeling problem. An ordinary copyable value permits multiple interchangeable representations. A class supplies identity, but copying its references permits multiple owners of the same instance.
A resource such as an operating-system handle may instead need one owner. The proposal introduces noncopyable structs and enums through suppression of the implicit Copyable capability. These values can have a deinitializer that runs when the unique instance's lifetime ends.
The declaration uses the new suppression syntax.
struct ResourceToken: ~Copyable {
let identifier: Int
init(identifier: Int) {
self.identifier = identifier
}
deinit {
print("Releasing token", identifier)
}
}
This small example demonstrates the type declaration and deinitializer without claiming to manage a real operating-system resource. A resource wrapper would put the corresponding cleanup operation in its deinitializer. The relevant property is that implicit copying cannot create a second owner of the same noncopyable value.
A class can model a resource with identity too, but the proposal explains the resulting shared ownership and reference-counting costs. A noncopyable struct represents a different ownership policy. It does not require every caller to participate in shared ownership merely to manage the resource's lifetime.
The first noncopyable model has deliberate restrictions
Swift 5.9 does not make every generic abstraction accept noncopyable arguments. SE-0390 explains that generic parameters and protocol requirements still assume Copyable. Suppressing that requirement in those contexts is outside the initial design.
As a result, noncopyable values cannot be used as ordinary generic type arguments, placed in tuples, or converted into Any. Protocol conformance is also restricted, with a specific exception for the Sendable marker protocol.
That exception does not turn a value into a generally usable existential. A noncopyable value conforming to Sendable still cannot be converted into an any Sendable value, because copying that existential would permit copying its contents. Concurrency compatibility and general type erasure remain separate questions.
These restrictions determine where a resource model can fit today. A type that prevents copying cannot be inserted into an existing abstraction whose contract permits copying. Lifting that assumption requires changes to the surrounding generic model, not a special case at each assignment.
This is the ownership work I find most convincing in the release. The compiler can reject an operation that would invalidate the intended resource model. The cost is that some otherwise familiar abstractions are unavailable for those values in this first version.
Passing a value also has an ownership policy
The release adds borrowing and consuming parameter modifiers and a consume operator. The announcement explains that the modifiers give the compiler information it can use to eliminate unnecessary copying and reference-counting operations when passing arguments.
The consume operator ends a variable binding's lifetime while transferring its contents without copying. That is a source-level statement about ownership, distinct from merely hoping an optimizer removes a copy.
These capabilities support performance-sensitive code while keeping the intent visible. Ordinary application code can continue to use familiar value and reference types. Code that must express a stricter lifetime or transfer rule now has language mechanisms for doing so.
The release presents this as the foundation for a larger ownership model. It does not claim that all low-level resource patterns or all forms of generic composition are complete in Swift 5.9. The limits in SE-0390 are part of the usable feature.
Parameter packs address an unrelated kind of duplication
Parameter packs allow generic types and functions to work over an arbitrary number of types. The release announcement illustrates this with a function that combines optional values of different wrapped types.
Without packs, that API needs an overload for each supported argument count. Each overload repeats the same idea while imposing an arbitrary maximum arity. Packs express the family of signatures without enumerating every count.
Callers do not have to spell out the pack machinery merely to pass their values. The complexity belongs in the generic declaration. That makes the feature relevant to library implementations even when application calls remain visually ordinary.
It is also separate from macros. A macro generates syntax, while a parameter pack lets a generic declaration express a relationship across multiple types directly. Choosing the appropriate mechanism avoids replacing type relationships with generated overloads when the language can represent the relationship itself.
C++ interoperability supports mixed implementations
Swift 5.9 supports calls across the Swift and C++ boundary in both directions for certain API shapes, including Objective-C++. The release announcement demonstrates importing a C++ function returning a vector of strings and processing its result in Swift.
That makes incremental use within an existing codebase possible without treating replacement of the whole codebase as a prerequisite. The announcement also warns that interoperability is actively evolving and some aspects may change as real mixed-language usage provides feedback.
The supported subset remains material. Bidirectional interoperability does not mean every C++ construct has a direct Swift representation. A library boundary still needs to fit the documented interoperability model.
Other release changes help organize larger codebases around those boundaries. The new package access level permits access between modules in one package without exposing declarations to package clients. Swift Package Manager supplies the associated compiler configuration automatically. A package can split implementation work across targets while keeping those declarations outside its public API.