Macros, property wrappers and result builders

Write a property wrapper, build a result builder, understand the macro system and its limits, and know how to read generated code when something breaks.

Property wrappers

@propertyWrapper
struct Clamped<Value: Comparable> {
    private var value: Value
    private let range: ClosedRange<Value>

    init(wrappedValue: Value, _ range: ClosedRange<Value>) {
        self.range = range
        self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
    }

    var wrappedValue: Value {
        get { value }
        set { value = min(max(newValue, range.lowerBound), range.upperBound) }
    }

    var projectedValue: Clamped<Value> { self }
}

struct Settings {
    @Clamped(0...100) var volume = 120      // stores 100
    @Clamped(1...12) var columns = 0        // stores 1
}

var settings = Settings()
settings.volume = 250                        // still 100
print(settings.$volume.wrappedValue)         // access the wrapper itself
  • wrappedValue is what the property reads and writes; projectedValue is exposed with a leading $.
  • The wrapper is a value type, so mutation through wrappedValue requires the wrapper to be mutating or a reference type.
  • init(wrappedValue:) lets the property be initialised inline; other initialisers take configuration arguments.
  • Property wrappers add a layer at every access. In a hot loop that matters, which is why the standard library uses them sparingly.

Result builders

@resultBuilder
struct HTMLBuilder {
    static func buildBlock(_ parts: String...) -> String {
        parts.joined()
    }

    static func buildOptional(_ part: String?) -> String {
        part ?? ""
    }

    static func buildEither(first part: String) -> String { part }
    static func buildEither(second part: String) -> String { part }

    static func buildArray(_ parts: [String]) -> String {
        parts.joined()
    }

    static func buildExpression(_ text: String) -> String { text }
}

func page(@HTMLBuilder _ content: () -> String) -> String {
    "<!doctype html><html><body>" + content() + "</body></html>"
}

let html = page {
    "<h1>Reports</h1>"
    if includeCharts {
        "<figure id=\"chart\"></figure>"
    }
    for row in rows {
        "<li>" + row + "</li>"
    }
}
Builder methodEnablesRequired
buildBlockMultiple statementsYes
buildOptionalif without an elseNo
buildEitherif and else, switchNo
buildArrayfor loopsNo
buildExpressionConverting an expression to the result typeNo

Macros and generated code

// A freestanding macro expands at the call site
let name = #function
let file = #filePath
let warning = #warning("remove before release")

// An attached macro augments a declaration: the compiler requires
// a peer declaration generated by a compiler plugin.
@freestanding(expression)
macro stringify<T>(_ value: T) -> (T, String) = #externalMacro(module: "MacrosImpl", type: "StringifyMacro")

@attached(member, names: named(init(from:)))
public macro Codable() = #externalMacro(module: "MacrosImpl", type: "CodableMacro")

// expand a macro to see exactly what it generated, without building:
// swiftc -dump-macro-expansions Sources/App/Model.swift

extension String {
    // a macro can replace boilerplate but not control flow in the caller
    var isNotEmpty: Bool { !isEmpty }
}
⚠️
A macro is code that writes code, so a mistake produces generated declarations with confusing diagnostics pointing at the expansion rather than your source. Use -dump-macro-expansions before debugging, and keep macros small and single-purpose.

FAQ

Can a macro change control flow in the caller?
No. Macros are hygienic and their expansion is limited to the declaration they attach to, plus names you explicitly declare. Anything that needs to rewrite surrounding code has to be a function or a result builder.
Where do property wrappers make the most sense?
At a boundary you repeat everywhere: validation, unit conversion, persistence keys, and dependency injection. One wrapper removes the same boilerplate from dozens of properties and makes the rule enforceable in one place.

Generics, opaque types and protocol design Memory management, ARC and performance

Last refreshed 2026-09-18.