Swift 5.6 makes existential types and generated builds explicit
Swift 5.6 adds the any spelling for existential types and package plugins for generated build commands. Both changes expose decisions that previously hid behind convenient syntax or external tooling.
A protocol name can describe a requirement on a concrete type or a value whose concrete type has been erased. Those uses look similar in Swift source, but the compiler has different information in each case. Swift 5.6 introduces any to make the second use visible. The distinction matters when designing an API, before a programmer encounters a limitation of erased type information.
The March 14 release announcement also introduces package manager build tools and command plugins. These changes give package authors a supported way to describe work that previously needed separate coordination. Concurrency checking gains an incremental migration path as well. Taken together, the release makes several important engineering choices expressible within the language and package system.
I appreciate Swift's tendency to put consequential distinctions into declarations. The language is easier to reason about when a reader can identify where information disappears. The new existential spelling is useful on that basis, even though adding a keyword does not itself improve the underlying operation's performance.
Any describes an existing abstraction
Holly Borla's SE-0335 proposal explains the problem with the old spelling. A plain protocol name in a type position can denote an existential. Its visual similarity to a generic constraint conceals the fact that the concrete stored type can change dynamically.
For a small example, consider a protocol that returns a textual description. A generic function can receive a specific conforming type. A stored existential can receive values of different conforming types through one declared type.
protocol Described {
func describe() -> String
}
struct Item: Described {
func describe() -> String { "item" }
}
func describe<T: Described>(_ value: T) -> String {
value.describe()
}
let value: any Described = Item()
let text = value.describe()
The generic declaration preserves a type parameter that represents the caller's concrete type. By contrast, the existential declaration expresses storage for a value conforming to the protocol, with the concrete identity erased from the static type of value. Writing any makes that choice apparent at the declaration where it happens.
SE-0335 explicitly says that any has the same semantics as existing existential types. It does not introduce a new container with different performance characteristics. The proposal describes an inline buffer for small values and dynamic storage when the value does not fit. It also identifies indirection and dynamic dispatch as costs associated with this abstraction. Writing any exposes the choice to the reader without removing those costs.
That distinction prevents a misleading interpretation of the release. Replacing a plain protocol type with any is a spelling change for an existing abstraction. Replacing existential storage with a generic design can be a larger API change, because the design then preserves relationships involving a particular concrete type. Those are separate decisions with different consequences for callers.
Erasure affects expressiveness as well as cost
The proposal's associated-type example explains why an existential does not automatically satisfy every use of its protocol as a generic constraint. Suppose a protocol has an associated type and a method consuming that type. A generic function can refer to the concrete conformer's associated type. An existential alone does not provide an arbitrary caller with a matching value to pass to that method.
The obstacle is the relationship between types. Knowing that some hidden type conforms does not identify the associated type required by a particular operation. A programmer who first meets protocols through existential storage can mistake that restriction for an arbitrary limitation of generics. The different spelling gives explanations a more accurate starting point.
Swift 5.6 permits any where existing existential types can be used. The proposal preserves the older spelling in Swift 5 language mode and describes a staged transition. Existing code therefore does not need a universal rewrite merely to adopt this compiler. This release establishes explicit notation while retaining compatibility with source that already uses protocol names for existential types.
The keyword also has limits. SE-0335 applies it to protocols and protocol compositions, with rules for metatypes. It is not a general annotation to put before a struct or an arbitrary type parameter. A concrete struct already names its type, so any would not describe a meaningful additional abstraction there.
Generated sources become build dependencies
Package extensibility addresses a different missing declaration. A package that needs generated source must describe when its generator runs and what files it produces. Without integration into the package build graph, that work can become an external prerequisite or a set of generated files committed by hand.
SE-0303 introduces package plugins and an initial build tool capability. The central separation is between configuring a command and executing the tool. A plugin constructs descriptions of commands for Swift Package Manager. The build system executes those commands at the appropriate point in the build.
This matters for incremental work. Inputs and outputs belong in the dependency graph that determines what must run. A generator invoked through an unrelated script does not automatically give that graph the information it needs. The plugin mechanism lets generation participate in package construction, including common cases such as producing Swift source from protocol definitions.
A plugin is a target with a declared capability. A package target opts into using it. SE-0303 gives the plugin information about the target and read-only access to package source, with designated build output locations for generated material. That arrangement makes the plugin's scope part of the package model. Source generation does not require unrestricted mutation of the package's own source directory.
The plugin itself is expected to remain small. The proposal initially limits its available libraries to the plugin API and Swift standard libraries. A tool invoked by a generated command can be a normal executable target with its own dependencies, or a supplied binary tool. This separation avoids requiring the build system to build arbitrary plugin dependencies before it can even construct the main build plan.
My least pleasant part of Swift development is the time spent waiting for builds. The plugin design interests me because it gives the build system explicit information about generated work. That is a structural improvement, although the release announcement does not establish that any particular package will build faster after adopting it.
Commands have a different activation model
The release also includes command plugins through SE-0332. The announcement distinguishes commands that users invoke directly from build tool plugins that participate in a build. Documentation generation and source formatting are examples of command intent. A custom command can supply its own verb for invocation through Swift Package Manager.
That distinction preserves an important difference in package workflows. Producing a generated source file needed for compilation is part of constructing a target. Formatting existing source is an action a user may request independently. The capability and activation model let tools describe which kind of work they perform.
The announcement identifies Swift-DocC as an early user of command plugin support. The documentation compiler is also part of the open-source release toolchain on macOS and Linux. Those details make plugin support more concrete than an extension point without a consumer, while keeping documentation generation separate from the compilation dependencies of an application target.
Concurrency adoption has dependency boundaries
SE-0337 addresses the difficulty of adding concurrency checking to an ecosystem one module at a time. A library may add concurrency annotations before its clients are ready, or a client may enable checking before its dependencies have been audited. Requiring the whole dependency graph to transition simultaneously would obstruct either direction.
Swift 5.6 suppresses Sendable diagnostics by default, with explicit adoption and the -warn-concurrency flag providing ways to enable checking. The proposal's @preconcurrency import mechanism can suppress or reduce diagnostics associated with incompletely annotated dependencies. It also describes warnings when a dependency's stronger annotations reveal an incorrect assumption in client code.
Suppressing a diagnostic does not establish safe behaviour. The proposal explains that a library's new annotations can reveal a bug that was already present in the client. Treating that discovery as a migration concern allows library authors to publish more accurate contracts without making every consumer fix every latent issue immediately.
The common practical consequence of these changes is that declarations carry more of the relevant engineering information. An existential records erased type identity. A plugin records how custom work enters the package process. Concurrency annotations describe constraints that have to survive a module boundary, with compatibility mechanisms for adopting them incrementally. In each case, the new declaration gives the compiler or build system information that previously required additional explanation outside the normal source model.