To merge two Swift struct values, write a function that chooses the source for each field and returns a new value. This macOS example combines two synthetic drawing-panel styles, with explicit rules for replacement, preservation, optional clearing, and invalid input.

Define what wins before writing the merge

The reader task is to calculate one drawing-panel style from two existing values on a Mac. Call the inputs base and incoming: these names describe their roles, while a field-by-field contract determines which value wins. The calculation uses only synthetic values in memory.

Swift documents structures as value types and supports constructing instances through initializers. Apple’s documented merge(_:uniquingKeysWith:) API belongs to Dictionary; it resolves duplicate keys through a closure. For this custom struct, define a merge function whose implementation expresses the intended field rules. Structures and Classes, Dictionary merge(_:uniquingKeysWith:).

Use the following illustrative contract. These are design choices for this example, rather than behavior supplied automatically by Swift.

Field Rule Example consequence
title Take incoming text, trim surrounding whitespace and newlines, then reject an empty result " Tile Study \n" becomes "Tile Study"
spacing Preserve the base value; require it to be nonnegative Base 12 wins over incoming 30
showsGrid Take the incoming Boolean exactly Incoming false replaces base true
caption Take the incoming optional exactly Incoming nil clears the base caption

This table distinguishes three operations that are easy to confuse: selecting a source, normalizing the selected value, and validating the result. The incoming spacing is deliberately unused. It cannot change the output, even if its value differs from the base spacing.

Foundation’s trimmingCharacters(in:) returns a new string with matching characters removed from both ends. Apple documents using .whitespacesAndNewlines for surrounding whitespace; a string made entirely of those characters becomes empty. The rejection of that empty result is our additional rule. Foundation trimmingCharacters(in:).

Run a complete local example on your Mac

Use a Swift 6 toolchain for this example. Swift.org provides macOS installation instructions through Swiftly and identifies Xcode as an option for Apple-platform development. Open Terminal and run swift --version to confirm the command is available. If it is missing, follow the installation instructions before continuing. Install Swift on macOS, Getting Started.

Save the following complete program as MergeStyles.swift in a working folder. It imports Foundation, defines both input values, performs the merge, checks the expected result, and exercises the two rejection cases. The calculation makes no network requests and stores no application data.

import Foundation

struct PanelStyle: Equatable, Sendable {
    let title: String
    let spacing: Int
    let showsGrid: Bool
    let caption: String?
}

enum StyleMergeError: Error {
    case emptyTitle
    case negativeSpacing
}

func mergeStyles(
    base: PanelStyle,
    incoming: PanelStyle
) throws -> PanelStyle {
    let title = incoming.title.trimmingCharacters(
        in: .whitespacesAndNewlines
    )
    guard !title.isEmpty else {
        throw StyleMergeError.emptyTitle
    }
    guard base.spacing >= 0 else {
        throw StyleMergeError.negativeSpacing
    }

    return PanelStyle(
        title: title,
        spacing: base.spacing,
        showsGrid: incoming.showsGrid,
        caption: incoming.caption
    )
}

let base = PanelStyle(
    title: "Shape Study",
    spacing: 12,
    showsGrid: true,
    caption: "Blue squares"
)
let incoming = PanelStyle(
    title: "  Tile Study \n",
    spacing: 30,
    showsGrid: false,
    caption: nil
)

let expected = PanelStyle(
    title: "Tile Study",
    spacing: 12,
    showsGrid: false,
    caption: nil
)

let result = try mergeStyles(base: base, incoming: incoming)
precondition(result == expected)
precondition(base.title == "Shape Study")
precondition(base.caption == "Blue squares")
precondition(incoming.title == "  Tile Study \n")

print(result.title)
print("spacing: \(result.spacing)")
print("grid: \(result.showsGrid)")
print("caption cleared: \(result.caption == nil)")

let blankTitle = PanelStyle(
    title: " \n\t",
    spacing: 30,
    showsGrid: false,
    caption: nil
)
do {
    _ = try mergeStyles(base: base, incoming: blankTitle)
    preconditionFailure("Expected emptyTitle")
} catch StyleMergeError.emptyTitle {
    print("Rejected blank title")
}

let negativeBase = PanelStyle(
    title: "Shape Study",
    spacing: -1,
    showsGrid: true,
    caption: "Blue squares"
)
do {
    _ = try mergeStyles(base: negativeBase, incoming: incoming)
    preconditionFailure("Expected negativeSpacing")
} catch StyleMergeError.negativeSpacing {
    print("Rejected negative spacing")
}

From the folder containing the file, run:

swift MergeStyles.swift

The expected output is shown below. Swift was unavailable in the research environment, so this program was inspected but not executed there.

Tile Study
spacing: 12
grid: false
caption cleared: true
Rejected blank title
Rejected negative spacing

The initializer is the central decision aid: every argument identifies either the selected input field or a named intermediate value. There is no mutation of either input. Swift’s value semantics support this approach; the fields here are themselves value types. If you later introduce a class-valued field, copying the enclosing struct does not independently copy the referenced class instance. Structures and Classes.

Validation happens before construction. A blank incoming title throws instead of silently restoring the base title, and negative base spacing throws instead of silently clamping it to zero. If both conditions fail, emptyTitle is reported first because that guard runs first. Callers should handle the error before using a result.

Decide whether an optional means clear or preserve

The main example treats incoming.caption as the complete desired caption. Its nil therefore means “the result has no caption.” This makes clearing explicit in the contract, even though the representation is just an optional.

A different requirement might say that incoming nil means “keep the base caption.” For that policy, the replacement initializer argument would be caption: incoming.caption ?? base.caption. Swift’s nil-coalescing operator selects its left operand when it contains a value and otherwise uses the right operand. This is a labeled replacement fragment, not a second complete program. Nil-coalescing operator.

That fallback policy cannot also use the same incoming nil to request clearing. Choose the meaning before adopting the expression. An empty string is another distinct value: with either policy, an incoming caption of "" remains an empty caption unless you explicitly add normalization.

When a future editing operation needs all three actions—preserve, replace, and clear—represent the action separately. The following independent, complete program illustrates a caption-only operation using a custom enum:

enum CaptionChange {
    case keep
    case set(String?)
}

func applying(
    _ change: CaptionChange,
    to caption: String?
) -> String? {
    switch change {
    case .keep:
        return caption
    case .set(let replacement):
        return replacement
    }
}

let original: String? = "Blue squares"
precondition(applying(.keep, to: original) == original)
precondition(applying(.set("Green circles"), to: original)
    == "Green circles")
precondition(applying(.set(nil), to: original) == nil)
print("Caption actions checked")

This enum is an illustrative extension of the contract. Keep the original two-value function when both inputs are complete styles; introduce an action type when the input actually describes edits.

Check precedence with deliberately different values

A useful merge check makes the competing fields disagree. If both inputs use spacing 12 and both enable the grid, an incorrect source choice can pass unnoticed. The sample instead uses spacing 12 versus 30, and true versus false, so its expected result exposes those choices.

Before adapting the function, check these boundary cases against the intended contract:

  • Incoming false must remain false; do not use Boolean OR to express replacement.
  • Incoming caption nil must clear the caption under the main policy.
  • Surrounding title whitespace must be removed, while internal spaces remain.
  • A whitespace-only incoming title must throw, without falling back to the base.
  • Base spacing 0 must succeed; negative base spacing must throw.
  • Swapping the arguments must apply the rules to the newly assigned roles.

The final point matters because this merge is order-dependent. Reversing the sample inputs selects "Shape Study", spacing 30, grid true, and caption "Blue squares". That is the result of the stated rules. Name both arguments at call sites so their roles remain visible.

When adding a field, extend the table, the initializer, and a check with competing values. If a future nested style contains several independent settings, decide whether the whole nested value is replaced or whether each of its fields has its own precedence. The current implementation only defines the four fields shown.

Keep concurrency separate from the merge policy

This calculation is synchronous: it reads its arguments, validates selected values, and returns a result or throws. It needs no task or actor to establish precedence. Swift documents asynchronous functions as functions that can suspend; adding asynchronous execution would not decide which field wins. Concurrency.

The example declares PanelStyle as Sendable to express that its values can cross concurrency boundaries. Apple requires a sendable struct to contain sendable members. That conformance concerns transferring values; the field-selection rules still come entirely from mergeStyles. Sendable.

For this local drawing-panel task, keep the contract beside the function and use visibly different sample inputs. The resulting value should be explainable one field at a time: a normalized incoming title, preserved base spacing, the incoming grid setting, and an incoming caption that can explicitly clear the previous value.

Sources