Swift Macros: The Complete Guide to Metaprogramming

Published on Β· Updated Β· 21 min

Wlad
Wlad
Founder & Swift Tech Lead

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:

ApproachAdvantagesDisadvantages

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

RoleSyntaxDescription

@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

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

ProposalTitleStatus

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.