Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Swift  Module 1 – Getting started: Swift 6.4, toolchains and your first program

Compiler errors, language modes and feature flags

Read a Swift diagnostic part by part, switch between the Swift 5 and Swift 6 language modes, turn on upcoming features early and control warnings by group.

  • Beginner
  • 15 minutes
  • Examples run with Swift 6.4 and Python 3.14.8
  • By MySmartCoPilot
Free: 15 minutes

What you will learn

  • Read a Swift diagnostic part by part, from its location and severity to its notes, fix-its and diagnostic group
  • Explain the Swift 5 and Swift 6 language modes and how a flag, a package setting or the tools version selects one
  • Configure upcoming features with -enable-upcoming-feature, its migration mode and Package.swift settings
  • Configure what warnings become with -Werror, -Wwarning, -warnings-as-errors and the @diagnose attribute

Before you start

What you’ll learn · Before you start

What you will learn

  • Read a Swift diagnostic part by part, from its location and severity to its notes, fix-its and diagnostic group
  • Explain the Swift 5 and Swift 6 language modes and how a flag, a package setting or the tools version selects one
  • Configure upcoming features with -enable-upcoming-feature, its migration mode and Package.swift settings
  • Configure what warnings become with -Werror, -Wwarning, -warnings-as-errors and the @diagnose attribute

Before you start

On this page

The 17-line file at the start of this lesson makes the compiler print 40 lines: the place of each problem, the lines around it, notes that point elsewhere and suggested fixes. And the same file can build cleanly or fail depending on its language mode: The set of language rules the compiler applies to your code: Swift 6, 5, 4.2 or 4. One compiler supports several modes, chosen with -language-mode or a package setting; the Swift 6 mode reports possible data races as errors.: a global counter that the Swift 5 mode accepts is an error in the Swift 6 mode.

This lesson reads a diagnostic: A message from the compiler about the code: an error, which stops the build, a warning, which does not, or a note that adds detail. Each starts with the file, the line and the column. part by part, switches between the two language modes and shows what decides the mode of a build. Then it turns on rules of the next language mode early, one feature at a time, and decides which warnings must stop a build, with flags on the command line, settings in Package.swift and an attribute in the code.

A diagnostic names the place, the problem and a fix

This file has one problem that is an error in the Swift 6 language mode and three that are warnings:

stall.swift: one error, three warnings Swift · stall.swift
// stall.swift: one error and three warnings, each with its diagnostic group.
struct Stall {
    static var openStalls = 10
}

@available(*, deprecated, renamed: "price(cups:)")
func oldPrice(cups: Int) -> Int { cups * 25 }

func price(cups: Int) -> Int { cups * 25 }

func order() {
    var cups = 3
    let note = "no sugar"
    print(oldPrice(cups: cups))
}

order()

Compiled in the Swift 6 mode, every diagnostic comes with the lines around its place:

Four diagnostics in the Swift 6 mode Python · diagnostics_session.py
from session import run, version

# stall.swift in the Swift 6 language mode: one error and three warnings. The build is meant to fail.
version()
run("""
!swiftc -language-mode 6 stall.swift -o stall
""")

Output

# Apple Swift version 6.4 (swiftlang-6.4.0.34.1)
$ swiftc -language-mode 6 stall.swift -o stall
stall.swift:3:16: error: static property 'openStalls' is not concurrency-safe because it is nonisolated global shared mutable state [#MutableGlobalVariable]
 1 | // stall.swift: one error and three warnings, each with its diagnostic group.
 2 | struct Stall {
 3 |     static var openStalls = 10
   |                |- error: static property 'openStalls' is not concurrency-safe because it is nonisolated global shared mutable state [#MutableGlobalVariable]
   |                |- note: convert 'openStalls' to a 'let' constant to make 'Sendable' shared state immutable
   |                |- note: add '@MainActor' to make static property 'openStalls' part of global actor 'MainActor'
   |                `- note: disable concurrency-safety checks if accesses are protected by an external synchronization mechanism
 4 | }
 5 | 

stall.swift:14:11: warning: 'oldPrice(cups:)' is deprecated: renamed to 'price(cups:)' [#DeprecatedDeclaration]
12 |     var cups = 3
13 |     let note = "no sugar"
14 |     print(oldPrice(cups: cups))
   |           |- warning: 'oldPrice(cups:)' is deprecated: renamed to 'price(cups:)' [#DeprecatedDeclaration]
   |           `- note: use 'price(cups:)' instead
15 | }
16 | 

stall.swift:12:9: warning: variable 'cups' was never mutated; consider changing to 'let' constant [#VariableNeverMutated]
10 | 
11 | func order() {
12 |     var cups = 3
   |         `- warning: variable 'cups' was never mutated; consider changing to 'let' constant [#VariableNeverMutated]
13 |     let note = "no sugar"
14 |     print(oldPrice(cups: cups))

stall.swift:13:9: warning: initialization of immutable value 'note' was never used; consider replacing with assignment to '_' or removing it [#NoUsage]
11 | func order() {
12 |     var cups = 3
13 |     let note = "no sugar"
   |         `- warning: initialization of immutable value 'note' was never used; consider replacing with assignment to '_' or removing it [#NoUsage]
14 |     print(oldPrice(cups: cups))
15 | }

[#DeprecatedDeclaration]: <https://docs.swift.org/compiler/documentation/diagnostics/deprecated-declaration>
[#MutableGlobalVariable]: <https://docs.swift.org/compiler/documentation/diagnostics/mutable-global-variable>
[#NoUsage]: <https://docs.swift.org/compiler/documentation/diagnostics/no-usage>
[#VariableNeverMutated]: <https://docs.swift.org/compiler/documentation/diagnostics/variable-never-mutated>
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 diagnostics_session.py

Each diagnostic has the same parts, and each part tells you something different:

Part In the session What it tells you
Location stall.swift:3:16 The file, the line and the column
Severity error, warning, note An error stops the build; a warning and a note do not
Message static property 'openStalls' is not … What is wrong, in words
Group [#MutableGlobalVariable] A stable name with its own page on docs.swift.org
Notes convert 'openStalls' to a 'let' constant … Ways to fix it, or other places involved
Fix-it let, @MainActor, _ (llvm style, below) The exact edit an editor offers to make

The diagnostic group: A stable name for a family of diagnostics, printed in brackets after the message, such as [#DeprecatedDeclaration]. Flags such as -Werror and the @diagnose attribute act on groups, and docs.swift.org explains each one. at the end of a message is the part to search for: the compiler lists each group’s page under its output, and the page explains the rule with examples. The fix-its are easier to see in the compact llvm style, which prints each one under the code, and the second build shows that the Swift 5 mode reports only the three warnings and writes the program:

Fix-its in the llvm style, then the Swift 5 mode Python · styles_session.py
from session import run, version

# The same file in the compact llvm style, which prints each fix-it under the code (meant to fail), then in the
# Swift 5 language mode, where only the warnings are left and the build succeeds.
version()
run("""
!swiftc -language-mode 6 -diagnostic-style llvm stall.swift -o stall
swiftc -language-mode 5 stall.swift -o stall
""")

Output

# Apple Swift version 6.4 (swiftlang-6.4.0.34.1)
$ swiftc -language-mode 6 -diagnostic-style llvm stall.swift -o stall
stall.swift:3:16: error: static property 'openStalls' is not concurrency-safe because it is nonisolated global shared mutable state [#MutableGlobalVariable]
    static var openStalls = 10
               ^
stall.swift:3:16: note: convert 'openStalls' to a 'let' constant to make 'Sendable' shared state immutable
    static var openStalls = 10
           ~~~ ^
           let
stall.swift:3:16: note: add '@MainActor' to make static property 'openStalls' part of global actor 'MainActor'
    static var openStalls = 10
               ^
    @MainActor 
stall.swift:3:16: note: disable concurrency-safety checks if accesses are protected by an external synchronization mechanism
    static var openStalls = 10
               ^
    nonisolated(unsafe) 
stall.swift:14:11: warning: 'oldPrice(cups:)' is deprecated: renamed to 'price(cups:)' [#DeprecatedDeclaration]
    print(oldPrice(cups: cups))
          ^
stall.swift:14:11: note: use 'price(cups:)' instead
    print(oldPrice(cups: cups))
          ^~~~~~~~
          price
stall.swift:12:9: warning: variable 'cups' was never mutated; consider changing to 'let' constant [#VariableNeverMutated]
    var cups = 3
    ~~~ ^
    let
stall.swift:13:9: warning: initialization of immutable value 'note' was never used; consider replacing with assignment to '_' or removing it [#NoUsage]
    let note = "no sugar"
    ~~~~^~~~
    _
[exit status 1]
$ swiftc -language-mode 5 stall.swift -o stall
stall.swift:14:11: warning: 'oldPrice(cups:)' is deprecated: renamed to 'price(cups:)' [#DeprecatedDeclaration]
12 |     var cups = 3
13 |     let note = "no sugar"
14 |     print(oldPrice(cups: cups))
   |           |- warning: 'oldPrice(cups:)' is deprecated: renamed to 'price(cups:)' [#DeprecatedDeclaration]
   |           `- note: use 'price(cups:)' instead
15 | }
16 | 

stall.swift:12:9: warning: variable 'cups' was never mutated; consider changing to 'let' constant [#VariableNeverMutated]
10 | 
11 | func order() {
12 |     var cups = 3
   |         `- warning: variable 'cups' was never mutated; consider changing to 'let' constant [#VariableNeverMutated]
13 |     let note = "no sugar"
14 |     print(oldPrice(cups: cups))

stall.swift:13:9: warning: initialization of immutable value 'note' was never used; consider replacing with assignment to '_' or removing it [#NoUsage]
11 | func order() {
12 |     var cups = 3
13 |     let note = "no sugar"
   |         `- warning: initialization of immutable value 'note' was never used; consider replacing with assignment to '_' or removing it [#NoUsage]
14 |     print(oldPrice(cups: cups))
15 | }

[#DeprecatedDeclaration]: <https://docs.swift.org/compiler/documentation/diagnostics/deprecated-declaration>
[#NoUsage]: <https://docs.swift.org/compiler/documentation/diagnostics/no-usage>
[#VariableNeverMutated]: <https://docs.swift.org/compiler/documentation/diagnostics/variable-never-mutated>

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 styles_session.py

Under each caret line, the llvm style prints the text of a fix-it: A ready-made edit attached to a diagnostic, such as replacing var with let. Editors offer to apply it, and -diagnostic-style llvm prints it under the code.: let in place of var, @MainActor before the property, price in place of oldPrice, _ in place of the unused name. An editor offers the same edits as buttons. When a build reports many errors, fix the first one first: later errors are often caused by the earlier ones, and they disappear with them.

Checkpoint

Check: Reading a diagnostic

  1. Question 1 of 2 A diagnostic starts with stall.swift:3:16: error:. What is 16?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The column of the problem on line 3

    stall.swift:3:16 points at line 3, column 16, where openStalls starts. Editors use the same three parts to jump to the place.

  2. Question 2 of 2 In the llvm style, the warning for var cups = 3 shows ~~~ ^ and, under it, let. What does that mean?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: It is a fix-it, replacing the marked var with let, which an editor can apply for you

    A fix-it is a ready-made edit attached to a diagnostic. The llvm style prints it as text under the code; an editor shows it as a button.

One compiler, several language modes

A language mode is the set of rules the compiler applies, and the Swift 6.4 compiler has four of them: Swift 6, 5, 4.2 and 4. The diagnostics documentation states it plainly: the compiler’s version and the language mode are different things. This program changes a global variable from a function, and its fixed version puts that function on the main actor:

counter.swift: a global changed by a function Swift · counter.swift
// counter.swift: a global variable changed by a function.
var counter = 0

func increment() {
    counter += 1
}

increment()
increment()
print("counter:", counter)
counter_fixed.swift: the function on the main actor Swift · counter_fixed.swift
// counter_fixed.swift: the same program, with increment() on the main actor.
var counter = 0

@MainActor
func increment() {
    counter += 1
}

increment()
increment()
print("counter:", counter)
The same program in two modes Python · modes_session.py
from session import run, version

# swiftc without a language mode, then the Swift 6 mode (meant to fail), then the fix in the Swift 6 mode.
version()
run("""
swiftc counter.swift -o counter
./counter
!swiftc -language-mode 6 counter.swift -o counter
swiftc -language-mode 6 counter_fixed.swift -o counter_fixed
./counter_fixed
""")

Output

# Apple Swift version 6.4 (swiftlang-6.4.0.34.1)
$ swiftc counter.swift -o counter
$ ./counter
counter: 2
$ swiftc -language-mode 6 counter.swift -o counter
counter.swift:5:5: error: main actor-isolated var 'counter' can not be mutated from a nonisolated context
 1 | // counter.swift: a global variable changed by a function.
 2 | var counter = 0
   |     `- note: mutation of this var is only permitted within the actor
 3 | 
 4 | func increment() {
   |      `- note: add '@MainActor' to make global function 'increment()' part of global actor 'MainActor'
 5 |     counter += 1
   |     `- error: main actor-isolated var 'counter' can not be mutated from a nonisolated context
 6 | }
 7 | 
[exit status 1]
$ swiftc -language-mode 6 counter_fixed.swift -o counter_fixed
$ ./counter_fixed
counter: 2

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 modes_session.py

A bare swiftc used the Swift 5 mode, and the program printed counter: 2. In the Swift 6 mode the same code is an error: a top-level variable of the program belongs to the main actor, and a function that is not on the main actor may not change it, because two threads could change it at once. The note proposes the fix that the third build applies, @MainActor on increment(), and the program prints the same line as before. The Swift 6 mode did not change what the code does; it refused code that could race, which the Swift 5 mode accepted.

Each kind of build chooses its mode in its own place:

  • swift or swiftc on files: -language-mode 6, or its older spelling -swift-version 6. Without either, the mode is Swift 5.
  • A SwiftPM package: the tools version decides, and from swift-tools-version: 6.0 on the mode is Swift 6. swiftLanguageModes: sets the modes of the whole package, and .swiftLanguageMode(.v5) in a target’s settings keeps that one target in Swift 5.
  • An Xcode project: the Swift Language Version build setting, SWIFT_VERSION.

In code, #if swift(>=6) asks for the language mode and #if compiler(>=6.4) for the compiler, which is how the doctor program of the install lesson told them apart.

Key idea

One compiler applies one of several sets of rules: a flag, a package’s tools version or an Xcode setting picks the language mode, and the Swift 6 mode refuses code that could race instead of changing what the code does.

Checkpoint

Check: Language modes

  1. Question 1 of 2 swiftc counter.swift -o counter builds, but the same file fails with -language-mode 6. Why?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: A bare swiftc uses the Swift 5 mode, and the Swift 6 mode rejects a global changed from a function that is not on the main actor

    Without a choice, swift and swiftc use the Swift 5 mode. The Swift 6 mode adds data-race checks, and counter.swift fails one of them until increment() is marked @MainActor.

  2. Question 2 of 2 A package with swift-tools-version 6.4 has one target, Legacy, that is not ready for the Swift 6 mode. What keeps every other target in Swift 6?

    Choose one answer.

    A hint

    The mode can be set per target.

    Show the answer: Question 2 of 2

    Answer: .swiftLanguageMode(.v5) in the swiftSettings of Legacy alone

    The migration guide shows exactly this: the package's tools version gives Swift 6 to every target, and one target that still requires Swift 5 says so with .swiftLanguageMode(.v5).

Upcoming features bring the next mode’s rules early

Moving a large project to a new language mode in one step would mean fixing every new error at once. An upcoming feature: One rule of a future language mode that can be turned on early, on its own, with -enable-upcoming-feature or .enableUpcomingFeature in Package.swift. is one rule of a future mode that you can turn on now, alone. Walk through the phases:

Timeline · 5 events in order From the Swift 5 mode to the next one Walk through the phases in order. Each says which rules apply and how a project moves on to the next.
  1. Swift 5 mode

    The rules most existing code was written for

    The default of a bare swift or swiftc, and of packages whose tools version is below 6.0. The checks that make data races compile errors are not part of it.

  2. Upcoming features

    Swift 6 rules, one at a time

    Most rules that the Swift 6 mode adds are also upcoming features, such as StrictConcurrency or GlobalConcurrency, and -enable-upcoming-feature turns one on in a Swift 5 target. The 6.4 compiler lists 15 features as enabled in 6.

  3. Swift 6 mode

    All of them on, data-race safety by default this track

    In the Swift 6 mode all 15 are on, and possible data races are compile errors. A build chooses it with -language-mode 6, a tools version of 6.0 or later, or the Swift Language Version setting of Xcode.

  4. Next mode

    Six features planned for Swift 7

    ExistentialAny, InternalImportsByDefault, MemberImportVisibility, InferIsolatedConformances, NonisolatedNonsendingByDefault and ImmutableWeakCaptures are planned for the next language mode, which the compiler calls 7. Each can be turned on today.

  5. Migration mode

    Warnings that prepare the code

    Four of the six offer a migration mode. With :migrate after the name, the feature stays off, and the compiler instead warns at each place that needs a change, with a fix-it that keeps the program's behaviour.

The timeline has 5 events, from Swift 5 mode to Migration mode.

The compiler can list the features it knows, with the mode that turns each one on. This file uses a protocol, a kind of type that a later module explains, in the way the next mode will reject:

receipt.swift: a protocol used as a type Swift · receipt.swift
// receipt.swift: a protocol used as a type, which the next language mode wants written as any Payment.
protocol Payment {
    var amount: Int { get }
}

struct Cash: Payment {
    let amount: Int
}

struct Card: Payment {
    let amount: Int
}

func describe(_ payment: Payment) -> String {
    "paid \(payment.amount) rupees"
}

let payments: [Payment] = [Cash(amount: 40), Card(amount: 250)]
for payment in payments {
    print(describe(payment))
}
Upcoming features, turned on early Python · features_session.py
from session import run, version

# Which upcoming features the compiler knows, one turned on early, its migration mode, and a misspelled name.
version()
run("""
swiftc -print-supported-features | grep '"enabled_in"'
swiftc -language-mode 6 -enable-upcoming-feature ExistentialAny receipt.swift -o receipt
swiftc -language-mode 6 -enable-upcoming-feature ExistentialAny:migrate -diagnostic-style llvm receipt.swift -o receipt
swiftc -language-mode 6 -enable-upcoming-feature ExistenialAny receipt.swift -o receipt
swiftc -language-mode 6 -enable-upcoming-feature ExistenialAny -Wwarning StrictLanguageFeatures receipt.swift -o receipt
""")

Output

# Apple Swift version 6.4 (swiftlang-6.4.0.34.1)
$ swiftc -print-supported-features | grep '"enabled_in"'
      { "name": "ConciseMagicFile", "enabled_in": "6" },
      { "name": "ForwardTrailingClosures", "enabled_in": "6" },
      { "name": "StrictConcurrency", "enabled_in": "6" },
      { "name": "BareSlashRegexLiterals", "enabled_in": "6" },
      { "name": "DeprecateApplicationMain", "enabled_in": "6" },
      { "name": "ImportObjcForwardDeclarations", "enabled_in": "6" },
      { "name": "DisableOutwardActorInference", "enabled_in": "6" },
      { "name": "IsolatedDefaultValues", "enabled_in": "6" },
      { "name": "GlobalConcurrency", "enabled_in": "6" },
      { "name": "InferSendableFromCaptures", "enabled_in": "6" },
      { "name": "ImplicitOpenExistentials", "enabled_in": "6" },
      { "name": "RegionBasedIsolation", "enabled_in": "6" },
      { "name": "DynamicActorIsolation", "enabled_in": "6" },
      { "name": "NonfrozenEnumExhaustivity", "enabled_in": "6" },
      { "name": "GlobalActorIsolatedTypesUsability", "enabled_in": "6" },
      { "name": "ExistentialAny", "migratable": true, "categories": ["ExistentialAny"], "enabled_in": "7" },
      { "name": "InternalImportsByDefault", "enabled_in": "7" },
      { "name": "MemberImportVisibility", "migratable": true, "categories": ["MemberImportVisibility"], "enabled_in": "7" },
      { "name": "InferIsolatedConformances", "migratable": true, "categories": ["IsolatedConformances"], "enabled_in": "7" },
      { "name": "NonisolatedNonsendingByDefault", "migratable": true, "categories": ["NonisolatedNonsendingByDefault"], "enabled_in": "7" },
      { "name": "ImmutableWeakCaptures", "enabled_in": "7" }
$ swiftc -language-mode 6 -enable-upcoming-feature ExistentialAny receipt.swift -o receipt
receipt.swift:14:26: warning: use of protocol 'Payment' as a type must be written 'any Payment'; this will be an error in a future Swift language mode [#ExistentialAny]
12 | }
13 | 
14 | func describe(_ payment: Payment) -> String {
   |                          `- warning: use of protocol 'Payment' as a type must be written 'any Payment'; this will be an error in a future Swift language mode [#ExistentialAny]
15 |     "paid \(payment.amount) rupees"
16 | }

receipt.swift:18:16: warning: use of protocol 'Payment' as a type must be written 'any Payment'; this will be an error in a future Swift language mode [#ExistentialAny]
16 | }
17 | 
18 | let payments: [Payment] = [Cash(amount: 40), Card(amount: 250)]
   |                `- warning: use of protocol 'Payment' as a type must be written 'any Payment'; this will be an error in a future Swift language mode [#ExistentialAny]
19 | for payment in payments {
20 |     print(describe(payment))

[#ExistentialAny]: <https://docs.swift.org/compiler/documentation/diagnostics/existential-any>
$ swiftc -language-mode 6 -enable-upcoming-feature ExistentialAny:migrate -diagnostic-style llvm receipt.swift -o receipt
receipt.swift:14:26: warning: use of protocol 'Payment' as a type must be written 'any Payment' [#ExistentialAny]
func describe(_ payment: Payment) -> String {
                         ^~~~~~~
                         any Payment
receipt.swift:18:16: warning: use of protocol 'Payment' as a type must be written 'any Payment' [#ExistentialAny]
let payments: [Payment] = [Cash(amount: 40), Card(amount: 250)]
               ^~~~~~~
               any Payment
$ swiftc -language-mode 6 -enable-upcoming-feature ExistenialAny receipt.swift -o receipt
$ swiftc -language-mode 6 -enable-upcoming-feature ExistenialAny -Wwarning StrictLanguageFeatures receipt.swift -o receipt
<unknown>:0: warning: 'ExistenialAny' is not a recognized upcoming feature [#StrictLanguageFeatures::UnrecognizedStrictLanguageFeatures]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 features_session.py

ExistentialAny turned two places into warnings that say they will be errors in a future language mode, so you can fix them while the code still builds. ExistentialAny:migrate left the feature off and printed the same two places with their fix-its, any Payment. The last two builds show a trap: a misspelled name, ExistenialAny, is ignored without a word, because projects must build with several compiler versions; the StrictLanguageFeatures warning group reports it.

In a package, the same switch is a setting of a target, .enableUpcomingFeature("ExistentialAny"). The package of the first program lesson already used one: ApproachableConcurrency, which, in the compiler’s source, stands for five features at once, DisableOutwardActorInference, GlobalActorIsolatedTypesUsability, InferIsolatedConformances, InferSendableFromCaptures and NonisolatedNonsendingByDefault. The concurrency modules of this track explain what they change.

Checkpoint

Check: Upcoming features

  1. Question 1 of 2 What does -enable-upcoming-feature ExistentialAny do in a Swift 6 build?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: It turns on one rule planned for the next language mode, which here reports warnings that will become errors

    Upcoming features let a project adopt the next mode's rules one at a time. ExistentialAny marked two places that the next mode will reject unless they are written as any Payment.

  2. Question 2 of 2 A target sets .enableUpcomingFeature("ExistentalAny"), and nothing changes. Why?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The name is misspelled, and unknown names are ignored unless the StrictLanguageFeatures warnings are on

    ExistentalAny is not a feature, and the compiler says nothing about it by default, as the session showed for another misspelling. -Wwarning StrictLanguageFeatures reports unknown names.

Warnings can be raised, kept or silenced by group

A warning never stops a build, which is why teams that want a clean build promote warnings to errors. Swift 6.4 gives that control per group, on the command line, in Package.swift and in the code. This file calls a deprecated function in two places, one of them inside a function marked with @diagnose; the second file asks @diagnose to lower an error:

fares.swift: a deprecated function, called twice Swift · fares.swift
// fares.swift: a deprecated function, called in two places.
@available(*, deprecated, renamed: "fare(km:)")
func oldFare(km: Int) -> Int { 20 + km * 8 }

func fare(km: Int) -> Int { 20 + km * 8 }

func monthlyPass() -> Int {
    oldFare(km: 12) * 44
}

@diagnose(DeprecatedDeclaration, as: ignored, reason: "the old ticket machines need it")
func ticketMachineFare() -> Int {
    oldFare(km: 12)
}

print(monthlyPass(), ticketMachineFare(), fare(km: 3))
downgrade.swift: an error that cannot be lowered Swift · downgrade.swift
// downgrade.swift: @diagnose asks for a warning, but this diagnostic is an error.
@diagnose(MutableGlobalVariable, as: warning)
struct Stall {
    static var openStalls = 10
}

print(Stall.openStalls)
One warning, four treatments, and an error Python · warnings_session.py
from session import run, version

# One warning, four ways of treating it; then an error that no setting can lower. Three builds are meant to fail.
version()
run("""
swiftc -language-mode 6 fares.swift -o fares
./fares
!swiftc -language-mode 6 -Werror DeprecatedDeclaration fares.swift -o fares
!swiftc -language-mode 6 -warnings-as-errors fares.swift -o fares
swiftc -language-mode 6 -warnings-as-errors -Wwarning DeprecatedDeclaration fares.swift -o fares
!swiftc -language-mode 6 downgrade.swift -o downgrade
""")

Output

# Apple Swift version 6.4 (swiftlang-6.4.0.34.1)
$ swiftc -language-mode 6 fares.swift -o fares
fares.swift:8:5: warning: 'oldFare(km:)' is deprecated: renamed to 'fare(km:)' [#DeprecatedDeclaration]
 6 | 
 7 | func monthlyPass() -> Int {
 8 |     oldFare(km: 12) * 44
   |     |- warning: 'oldFare(km:)' is deprecated: renamed to 'fare(km:)' [#DeprecatedDeclaration]
   |     `- note: use 'fare(km:)' instead
 9 | }
10 | 

[#DeprecatedDeclaration]: <https://docs.swift.org/compiler/documentation/diagnostics/deprecated-declaration>
$ ./fares
5104 116 44
$ swiftc -language-mode 6 -Werror DeprecatedDeclaration fares.swift -o fares
fares.swift:8:5: error: 'oldFare(km:)' is deprecated: renamed to 'fare(km:)' [#DeprecatedDeclaration]
 6 | 
 7 | func monthlyPass() -> Int {
 8 |     oldFare(km: 12) * 44
   |     |- error: 'oldFare(km:)' is deprecated: renamed to 'fare(km:)' [#DeprecatedDeclaration]
   |     `- note: use 'fare(km:)' instead
 9 | }
10 | 

[#DeprecatedDeclaration]: <https://docs.swift.org/compiler/documentation/diagnostics/deprecated-declaration>
[exit status 1]
$ swiftc -language-mode 6 -warnings-as-errors fares.swift -o fares
fares.swift:8:5: error: 'oldFare(km:)' is deprecated: renamed to 'fare(km:)' [#DeprecatedDeclaration]
 6 | 
 7 | func monthlyPass() -> Int {
 8 |     oldFare(km: 12) * 44
   |     |- error: 'oldFare(km:)' is deprecated: renamed to 'fare(km:)' [#DeprecatedDeclaration]
   |     `- note: use 'fare(km:)' instead
 9 | }
10 | 

[#DeprecatedDeclaration]: <https://docs.swift.org/compiler/documentation/diagnostics/deprecated-declaration>
[exit status 1]
$ swiftc -language-mode 6 -warnings-as-errors -Wwarning DeprecatedDeclaration fares.swift -o fares
fares.swift:8:5: warning: 'oldFare(km:)' is deprecated: renamed to 'fare(km:)' [#DeprecatedDeclaration]
 6 | 
 7 | func monthlyPass() -> Int {
 8 |     oldFare(km: 12) * 44
   |     |- warning: 'oldFare(km:)' is deprecated: renamed to 'fare(km:)' [#DeprecatedDeclaration]
   |     `- note: use 'fare(km:)' instead
 9 | }
10 | 

[#DeprecatedDeclaration]: <https://docs.swift.org/compiler/documentation/diagnostics/deprecated-declaration>
$ swiftc -language-mode 6 downgrade.swift -o downgrade
downgrade.swift:4:16: error: static property 'openStalls' is not concurrency-safe because it is nonisolated global shared mutable state [#MutableGlobalVariable]
2 | @diagnose(MutableGlobalVariable, as: warning)
3 | struct Stall {
4 |     static var openStalls = 10
  |                |- error: static property 'openStalls' is not concurrency-safe because it is nonisolated global shared mutable state [#MutableGlobalVariable]
  |                |- note: convert 'openStalls' to a 'let' constant to make 'Sendable' shared state immutable
  |                |- note: add '@MainActor' to make static property 'openStalls' part of global actor 'MainActor'
  |                `- note: disable concurrency-safety checks if accesses are protected by an external synchronization mechanism
5 | }
6 | 

[#MutableGlobalVariable]: <https://docs.swift.org/compiler/documentation/diagnostics/mutable-global-variable>
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 warnings_session.py

Only the call in monthlyPass is ever reported. -Werror DeprecatedDeclaration makes that group an error, -warnings-as-errors makes every warning one, and -warnings-as-errors -Wwarning DeprecatedDeclaration keeps the policy for everything except that group. Step through the decision the compiler makes for each call:

Interactive · stage by stage What a warning becomes Step through the decision for the deprecated call to oldFare, from its group to what the build reports.
What a warning becomes, step 6 of 6. Showing The diagnostic (errors are never changed), Command-line flags (this group a warning again), @diagnose in the code (ignored in that function), What the build reports (one warning, build succeeds).The diagnosticerrors are never changedCommand-line flagsthis group a warning again@diagnose in the codeignored in that functionWhat the build reportsone warning, build succeeds

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. A deprecated call. Calling oldFare is a warning of the group DeprecatedDeclaration, as the first build of the session showed. It starts as a warning, and the next two stages may change that.
  2. -warnings-as-errors. The flags apply from left to right. -warnings-as-errors makes every warning an error, so on its own it stops the build at the call in monthlyPass.
  3. -Wwarning DeprecatedDeclaration. The next flag names the group and makes it a warning again. For each group the last flag wins, so with the flags in the other order the call would stay an error.
  4. @diagnose on a function. Inside ticketMachineFare, its @diagnose attribute comes last and says ignored, so that call is never reported, whatever the flags say. The call in monthlyPass has no attribute around it.
  5. The result. The call in monthlyPass is reported as a warning and the build goes on; the call in ticketMachineFare is not reported at all.
  6. Errors stay errors. Only warnings can be changed. An error, such as the mutable global of downgrade.swift in the Swift 6 mode, stays an error under every flag and every @diagnose.

Version note

-Werror and -Wwarning arrived in Swift 6.1, .treatWarning and .treatAllWarnings in Swift 6.2, and @diagnose in Swift 6.4. An older compiler does not know the newer ones, so pin the toolchain before you rely on them.

The same choices exist in each place a project keeps its settings, the command line, the target settings of Package.swift and the code:

  • Every warning an error: -warnings-as-errors, or .treatAllWarnings(as: .error) on a target. The code has no switch for all groups at once.
  • One group an error: -Werror DeprecatedDeclaration, .treatWarning("DeprecatedDeclaration", as: .error), or @diagnose(DeprecatedDeclaration, as: error) on a declaration.
  • One group a warning again: -Wwarning DeprecatedDeclaration, .treatWarning("DeprecatedDeclaration", as: .warning), or @diagnose(DeprecatedDeclaration, as: warning).
  • One group not reported at all: only @diagnose(DeprecatedDeclaration, as: ignored, reason: "…"), and only inside the declaration it marks; no flag hides a group for a whole module.

Silencing a check instead of fixing the code

nonisolated(unsafe) static var openStalls = 10 makes the error of stall.swift go away, but it only tells the compiler to stop checking. Its documentation keeps it for state you protect yourself, with a lock for example. Make the value a let, or put it on the main actor, and the compiler can keep checking for you.

Checkpoint

Check: Controlling warnings

  1. Question 1 of 2 Which flags keep every warning an error except the warnings about deprecated declarations?

    Choose one answer.

    A hint

    The flags apply from left to right.

    Show the answer: Question 1 of 2

    Answer: -warnings-as-errors -Wwarning DeprecatedDeclaration

    The flags form one list applied from left to right, and for each group the last flag wins: first make every warning an error, then keep the one group a warning.

  2. Question 2 of 2 downgrade.swift puts @diagnose(MutableGlobalVariable, as: warning) on a struct with a static var. What happens in the Swift 6 mode?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: It is still an error, because flags and @diagnose change only warnings

    SE-0522 says that error diagnostics cannot be controlled by flags or by @diagnose, even in a group the attribute names. Fix the code: a let, or the main actor.

Practise: three programs, three diagnostics

Each of these programs has one error, a different one each time. Read each diagnostic, decide the fix it points to, and compare with the fixed version at the end of the session:

detective_1.swift Swift · detective_1.swift
func fare(km: Int) -> Int { 20 + km * 8 }
print(fare(12))
detective_2.swift Swift · detective_2.swift
let seats = 40
seats -= 2
print(seats)
detective_3.swift Swift · detective_3.swift
let price = 25
let tax = 0.05
print(price * tax)
detectives_fixed.swift: one fix for each Swift · detectives_fixed.swift
// detectives_fixed.swift: the three programs, each fixed the way its diagnostic suggests.
func fare(km: Int) -> Int { 20 + km * 8 }
print(fare(km: 12))

var seats = 40
seats -= 2
print(seats)

let price = 25
let tax = 0.05
print(Double(price) * tax)
Three errors, then the fixed program Python · detective_session.py
from session import run, version

# Three programs with one error each (the builds are meant to fail), then the fixed version, which builds and runs.
version()
run("""
!swiftc -language-mode 6 detective_1.swift -o detective
!swiftc -language-mode 6 detective_2.swift -o detective
!swiftc -language-mode 6 detective_3.swift -o detective
swiftc -language-mode 6 detectives_fixed.swift -o fixed
./fixed
""")

Output

# Apple Swift version 6.4 (swiftlang-6.4.0.34.1)
$ swiftc -language-mode 6 detective_1.swift -o detective
detective_1.swift:2:12: error: missing argument label 'km:' in call
1 | func fare(km: Int) -> Int { 20 + km * 8 }
2 | print(fare(12))
  |            `- error: missing argument label 'km:' in call
3 | 
[exit status 1]
$ swiftc -language-mode 6 detective_2.swift -o detective
detective_2.swift:2:7: error: left side of mutating operator isn't mutable: 'seats' is a 'let' constant
1 | let seats = 40
  | `- note: change 'let' to 'var' to make it mutable
2 | seats -= 2
  |       `- error: left side of mutating operator isn't mutable: 'seats' is a 'let' constant
3 | print(seats)
4 | 
[exit status 1]
$ swiftc -language-mode 6 detective_3.swift -o detective
detective_3.swift:3:13: error: binary operator '*' cannot be applied to operands of type 'Int' and 'Double'
1 | let price = 25
2 | let tax = 0.05
3 | print(price * tax)
  |             |- error: binary operator '*' cannot be applied to operands of type 'Int' and 'Double'
  |             `- note: overloads for '*' exist with these partially matching parameter lists: (Double, Double), (Duration, Double), (Int, Int)
4 | 
[exit status 1]
$ swiftc -language-mode 6 detectives_fixed.swift -o fixed
$ ./fixed
116
38
1.25

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 detective_session.py

The label km: was missing from the call, the note pointed at the let that had to become a var, and Int and Double do not mix in arithmetic until one is converted. None of the fixes needed more than the diagnostic and its notes. Then the questions below go through the whole lesson, weakest idea first:

Practice that adapts to you

Practise this lesson

10 questions about 4 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 10 Which kind of diagnostic stops the build?

    Choose one answer.

    Show the answer: Question 1 of 10

    Answer: An error

    Errors stop the build; warnings and notes do not, unless a setting such as -warnings-as-errors turns warnings into errors.

  2. Question 2 of 10 What is the quickest way to learn more about a diagnostic marked [#MutableGlobalVariable]?

    Choose one answer.

    Show the answer: Question 2 of 10

    Answer: Open the page of that group on docs.swift.org, which the compiler lists under its output

    The group name is stable across compiler versions, which makes it the right thing to search for and to put in flags such as -Werror.

  3. Question 3 of 10 Which condition is true only when the code is compiled in the Swift 6 language mode or a later one?

    Choose one answer.

    Show the answer: Question 3 of 10

    Answer: #if swift(>=6)

    Two different versions, two conditions: swift() for the language mode and compiler() for the compiler, as The Swift Programming Language explains.

  4. Question 4 of 10 Where do you choose the language mode of an Xcode project?

    Choose one answer.

    Show the answer: Question 4 of 10

    Answer: In the Swift Language Version build setting, SWIFT_VERSION

    Each kind of build keeps its mode in its own place: a flag for swiftc, the manifest for a package, a build setting for an Xcode project.

  5. Question 5 of 10 What does -enable-upcoming-feature ExistentialAny:migrate do?

    Choose one answer.

    Show the answer: Question 5 of 10

    Answer: It leaves the feature off and warns at each place that needs a change, with a fix-it

    The documentation describes the migration mode as warnings with the fix-its needed to keep the code working once the feature is on. Apply them, then turn the feature on for real.

  6. Question 6 of 10 The manifest that swift package init writes turns on ApproachableConcurrency. What is it?

    Choose one answer.

    Show the answer: Question 6 of 10

    Answer: One name that turns on five concurrency features at once

    ApproachableConcurrency is a shorthand. New packages turn it on, which is one reason a package and a bare swiftc build can behave differently; the concurrency modules explain the five features.

  7. Question 7 of 10 How many upcoming features does the 6.4 compiler list as planned for the next language mode, with enabled_in 7?

    Type a number.

    Show the answer: Question 7 of 10

    Answer: 6 features

    The session lists 15 features enabled in 6 and 6 enabled in 7: ExistentialAny, InternalImportsByDefault, MemberImportVisibility, InferIsolatedConformances, NonisolatedNonsendingByDefault and ImmutableWeakCaptures.

  8. Question 8 of 10 In Package.swift, which settings of a target make every warning an error except the deprecation warnings?

    Choose one answer.

    Show the answer: Question 8 of 10

    Answer: .treatAllWarnings(as: .error), then .treatWarning("DeprecatedDeclaration", as: .warning)

    Since Swift 6.2, treatAllWarnings and treatWarning set warning behaviour per target, the same way the compiler's flags do on the command line.

  9. Question 9 of 10 One function must keep calling a deprecated API for a few months, while the rest of the module must not call it at all. What fits best?

    Choose one answer.

    Show the answer: Question 9 of 10

    Answer: -Werror DeprecatedDeclaration for the module, and @diagnose(DeprecatedDeclaration, as: ignored, reason: "…") on that one function

    Module-wide flags set the policy; @diagnose makes a documented exception for one declaration. When the old API goes, delete the attribute and the policy covers everything again.

  10. Question 10 of 10 Which fix for the MutableGlobalVariable error of stall.swift keeps the compiler checking?

    Choose one answer.

    Show the answer: Question 10 of 10

    Answer: static let openStalls = 10, when the value never changes

    The notes offered three fixes: a let, the main actor, or nonisolated(unsafe). The first two let the compiler keep proving the code safe; the third asks it to trust you.

Interview questions

Warm-up: what are errors, warnings and notes in Swift, and what does a name such as [#DeprecatedDeclaration] mean?

An error means the code cannot be compiled as written, so the build stops. A warning points at code that compiles but is probably wrong or out of date, such as a deprecated call or a var that is never changed, and the build goes on. A note adds detail to an error or a warning: another place involved, or a suggested fix. The name in brackets is the diagnostic group, a stable identifier of that kind of diagnostic with its own page on docs.swift.org. Groups are what you control: -Werror DeprecatedDeclaration turns that group’s warnings into errors, -Wwarning keeps them warnings, and @diagnose changes them for one declaration. Errors themselves cannot be lowered, by flags or by @diagnose.

Key takeaways

  • A diagnostic gives the location, the severity, the message and the group, followed by notes and fix-its; fix the first error first, and look up the group on docs.swift.org.
  • One compiler supports several language modes: swift and swiftc default to Swift 5, a package with tools version 6.0 or later builds in Swift 6, and -language-mode or .swiftLanguageMode(.v5) chooses explicitly.
  • The Swift 6 mode turns possible data races into errors, such as a global variable changed from a function that is not on the main actor.
  • Upcoming features turn on single rules of a future mode early, and their :migrate form warns with fix-its instead; a misspelled feature name is ignored unless StrictLanguageFeatures is turned on.
  • Warnings are controlled by group with -warnings-as-errors, -Werror and -Wwarning, applied left to right, with .treatWarning and .treatAllWarnings in Package.swift and with @diagnose in code; errors cannot be lowered.

Terms in this lesson

diagnostic
A message from the compiler about the code: an error, which stops the build, a warning, which does not, or a note that adds detail. Each starts with the file, the line and the column.
diagnostic group
A stable name for a family of diagnostics, printed in brackets after the message, such as [#DeprecatedDeclaration]. Flags such as -Werror and the @diagnose attribute act on groups, and docs.swift.org explains each one.
fix-it
A ready-made edit attached to a diagnostic, such as replacing var with let. Editors offer to apply it, and -diagnostic-style llvm prints it under the code.
language mode
The set of language rules the compiler applies to your code: Swift 6, 5, 4.2 or 4. One compiler supports several modes, chosen with -language-mode or a package setting; the Swift 6 mode reports possible data races as errors.
upcoming feature
One rule of a future language mode that can be turned on early, on its own, with -enable-upcoming-feature or .enableUpcomingFeature in Package.swift.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.