Swift Macros: The Complete Guide to Metaprogramming
Published on Β· Updated Β· 21 min
What is a Swift Macro?
A macro is a source code transformation that runs at compile time. Unlike C macros that perform simple text replacements, Swift macros work on the abstract syntax tree (AST) and are entirely type-safe.
The main characteristics:
Code generation at compile time, not runtime
Full type checking on generated code
Execution in a secure sandbox environment
Ability to inspect generated code in Xcode
Native integration with SwiftPM
Macros solve problems that neither generics, protocols, nor property wrappers could elegantly solve.
Before Macros
Before Swift 5.9, developers used several approaches to reduce boilerplate:
| Approach | Advantages | Disadvantages |
|---|---|---|
Property Wrappers | Built into the language | Limited to properties |
Sourcery | Very powerful | External tool, maintenance |
Protocols + Extensions | Standard Swift | No code generation |
Code generation scripts | Flexible | Outside the compiler |
Macros combine the best: Sourcery's power, native integration, and compiler safety.
Macros Timeline
Swift 5.9 β September 2023
Introduction of macros with three foundational proposals:
SE-0382: Expression Macros
SE-0389: Attached Macros
SE-0397: Freestanding Declaration Macros
Swift 6.0 β September 2024
Stabilization and massive adoption. Apple introduces @Observable and @Model.
Swift 6.1+ β 2025
Continuous improvements to SwiftSyntax and new proposals under discussion (Body Macros SE-0415).
The Two Macro Families
Swift distinguishes two types of macros based on their usage syntax:
Freestanding Macros
Use the # prefix and can appear as expressions or declarations:
Attached Macros
Use the @ prefix and attach to an existing declaration:
Macro Roles Table
| Role | Syntax | Description |
|---|---|---|
@freestanding(expression) | #macro() | Returns a value |
@freestanding(declaration) | #macro() | Generates declarations |
@attached(peer) | @Macro | Adds declarations alongside |
@attached(accessor) | @Macro | Adds get/set/willSet/didSet |
@attached(memberAttribute) | @Macro | Adds attributes to members |
@attached(member) | @Macro | Adds new members |
@attached(extension) | @Macro | Adds conformances |
A macro can combine multiple roles.
Freestanding Expression Macros
Expression macros (@freestanding(expression)) return a value usable in code:
Example: #stringify
The #stringify macro returns a tuple with the value and its textual representation:
Example: #URL with Validation
Example: #Predicate
Freestanding Declaration Macros
Declaration macros (@freestanding(declaration)) generate new declarations:
Attached Peer Macros
Peer macros add declarations "alongside" the annotated declaration:
Attached Accessor Macros
Accessor macros add accessors (get, set, willSet, didSet):
Attached Member Macros
Member macros add new members to a type:
Attached Extension Macros
Extension macros add protocol conformances:
Attached MemberAttribute Macros
MemberAttribute macros apply attributes to all members:
Apple Macros in the Standard Library
Apple extensively uses macros in its own frameworks:
@Observable (Observation Framework)
@Model (SwiftData)
#expect and #require (Swift Testing)
#Predicate (Foundation)
Creating Your First Macro β Step by Step
Let's create an @AutoDescription macro that generates a readable description.
Package Structure
Package.swift
Macro Declaration
Macro Implementation
Usage
SwiftSyntax β The Core of Macros
SwiftSyntax is the library that allows manipulating the Swift AST.
Main Protocols
Navigating the AST
SwiftSyntaxBuilder β Generating Code
SwiftSyntaxBuilder allows building code with an expressive syntax:
MacroExpansionContext
The context provides useful information:
Debugging and Testing
Expand Macro in Xcode
Right-click on a macro β "Expand Macro" to see the generated code:
Macro Unit Testing
SwiftSyntax provides testing utilities:
Custom Diagnostics
Create informative error messages:
Popular Ecosystem Macros
swift-dependencies (@Dependency)
swift-composable-architecture (@Reducer)
Other Popular Macros
The community has created many open source macros:
@CodableKey: JSON key customization
@Builder: Automatic builder pattern
@EnumSubset: Type-safe enum subsets
@Copyable: Automatic copy(with:) method
@DefaultValue: Default values for Codable
See: Awesome Swift Macros
Best Practices
When to Use Macros
Macros are appropriate for:
Eliminating repetitive boilerplate
Generating code based on existing structure
Adding automatic conformances
Validating data at compile time
When to Avoid Macros
Macros are not ideal for:
Complex business logic (prefer functions)
Simple cases covered by protocols
Transformations that change frequently
Compilation Performance
Macros run at every compilation. Keep them lightweight:
SwiftSyntax Versioning
SwiftSyntax evolves with each Swift version. Specify a compatible version:
Limitations and Security
Execution Sandbox
Macros run in an isolated environment:
No file system access
No network
No arbitrary code execution
Limited memory and time
What Macros Cannot Do
Compile-Time Verification
Generated code is fully verified:
Official Resources
Apple Documentation
Swift Evolution Proposals
| Proposal | Title | Status |
|---|---|---|
SE-0382 | Expression Macros | Implemented (Swift 5.9) |
SE-0389 | Attached Macros | Implemented (Swift 5.9) |
SE-0394 | Package Manager Support for Custom Macros | Implemented (Swift 5.9) |
SE-0397 | Freestanding Declaration Macros | Implemented (Swift 5.9) |
SE-0415 | Function Body Macros | Under discussion |
Community
Swift Macros represent a powerful tool for any serious Swift developer. Mastered correctly, they allow writing cleaner, safer, and more maintainable code.