Embedded Swift removes runtime requirements to fit firmware
The new microcontroller examples demonstrate a compilation model built around specialization and restricted dynamic behavior, with integrations for existing board SDKs.
A microcontroller cannot absorb the supporting libraries of a desktop language simply because the application itself is small. The runtime can exceed the available memory before the firmware does useful work. Embedded Swift addresses that problem by changing what the compiler must resolve ahead of execution, and by excluding language operations that require substantial runtime machinery.
The April 3 announcement introduces a repository of working examples across ARM and RISC-V microcontrollers. It includes STM32 boards, Raspberry Pi Pico and Nordic Semiconductor boards, with RISC-V ESP32 boards also represented. Some examples build standalone Swift code. Others bridge vendor SDKs, which is an important accommodation for firmware that already depends on a board's existing software.
Embedded Swift is an experimental compilation mode at this point. The announcement directs users to development snapshots and explicitly says the mode is unavailable in release versions. The examples provide practical starting points for that experiment. They do not announce that ordinary Swift packages can now run unchanged on every microcontroller.
I find the subset approach appealing because much of Swift's value comes from how it expresses ordinary data and control flow. Those properties remain useful when a program has no need for reflection or binary-compatible replacement of a library. A firmware target gives the compiler a different set of constraints to optimize around.
The runtime has a cost before application code
The original Embedded Swift vision estimates the ordinary runtime and standard library at roughly 5 MB. Its target environments include boards with tens or hundreds of kilobytes of memory. The document also considers low-level software whose restrictions concern dependencies and allocation, even when the available memory is less severe.
The problem is partly dynamic language behavior. Reflection needs information describing types at runtime. Separately compiled generic code needs mechanisms to operate on types it does not know concretely when compiled. Library evolution preserves the ability for a binary library's implementation to change without rebuilding every client. These capabilities have implementation costs even when a particular firmware application needs little of them.
Embedded Swift trades away those capabilities to obtain smaller standalone programs. The announcement names runtime reflection, ABI stability and existentials among the excluded features. These exclusions are related. Each removes a reason to preserve general type information and operations on that information in the finished program.
This is more specific than asking an optimizer to make a normal application smaller. Dead-code removal can eliminate functions that nothing calls, but the compilation model may still require shared runtime support for the operations that remain. Changing the supported operations gives the compiler permission to remove entire classes of runtime dependency.
Specialization resolves generic operations early
The vision requires specialization of generic and protocol uses at compile time. A generic function remains useful in source, but the compiler must know the concrete types at its use sites. It can then generate implementations for those types without carrying a general runtime mechanism for unknown types into the firmware.
That requirement crosses library boundaries. A precompiled generic function whose implementation is hidden cannot always be specialized by its caller. The design therefore makes library implementations available through serialized compiler representation. The vision describes this as providing function bodies in SIL so application compilation can specialize the code it uses.
The same approach applies to the standard library. Standard library abstractions remain available to source code, while their implementations can become part of the application's specialized compilation. Unused parts can then disappear through dead-code stripping. The intent is to make binary cost follow the program's actual operations more closely.
Protocol use needs a similar distinction. A generic function constrained to a protocol can be specialized for a known conforming type. An existential value permits operations on a value whose concrete type is hidden behind a runtime representation. The original vision excludes existentials, while retaining protocol-based abstractions that specialization can resolve.
For firmware authors, that affects API design. A statically known generic relationship can fit the compilation model even when a collection of arbitrary type-erased objects does not. The restriction concerns what information must remain available at runtime. It does not require abandoning every abstraction that uses a protocol in its source declaration.
Some familiar operations depend on metadata
Reflection and Mirror are unavailable in the original design. Ordinary print also needs a replacement because printing arbitrary values can depend on reflection. That is a useful example of a convenience whose implementation is less local than its call syntax suggests.
Existential restrictions also affect error handling. The vision notes that thrown errors normally have the existential type any Error, and consequently says thrown error types need restrictions. As of this document, some of those details remain design questions. A post about the experimental mode cannot treat the vision's possible syntax as a settled application interface.
Metatypes receive restrictions for the same reason. Passing a concrete type as a compile-time hint can be compatible with specialization. Using type information for a runtime downcast requires capabilities the compilation mode removes. The original document uses these contrasting cases to show that the occurrence of a type parameter in source does not by itself determine the runtime cost.
Objective-C interoperability is excluded because it requires its runtime. C and C++ interoperability remain part of the design. That choice is consequential for microcontrollers because vendor libraries do not have to be rewritten wholesale before Swift can participate. The examples' SDK integrations demonstrate this intended route into existing firmware environments.
Allocation is a separate decision
The original vision distinguishes allocating Embedded Swift from a stricter non-allocating subset. Removing the full desktop runtime does not require forbidding all heap use. The allocating form can retain classes and other heap-dependent constructs with a much smaller supporting runtime.
Reference counting still needs implementation when a program uses reference-counted values. The vision describes a small runtime supporting object allocation and retain and release operations, with memory allocation supplied by the platform. These calls have specific purposes. A standalone binary should not be described as having no supporting code merely because it avoids the ordinary Swift runtime distribution.
The non-allocating form imposes further restrictions. Instantiating classes and constructing indirect enum cases are excluded. The document also excludes escaping closures and the dynamic containers that depend on these facilities. Arrays and strings therefore need separate consideration in an environment that cannot allocate dynamically.
I would not equate a preference for Swift value types with a guarantee of allocation-free execution. The vision's distinction makes the actual requirement explicit. A firmware component with a heap and a component forbidden from allocating have different constraints, even when both use the same source language.
The compiler's ability to expose dependencies matters here. The vision illustrates inspecting object-file symbols to see what platform support remains necessary. Its command examples describe the proposed compilation model, rather than a final build recipe for the newly published boards. The board examples are the appropriate place to obtain concrete toolchain and linker instructions.
Board integration remains part of the program
The announcement deliberately includes different build systems and integration approaches. That is useful evidence about scope. A language mode can produce suitable machine code while leaving startup, linking and interaction with a vendor SDK to the surrounding firmware build.
Standalone Swift and SDK-backed Swift answer different integration questions. A standalone example shows how little supporting software a small program needs. An SDK-backed example shows how Swift code can reuse an existing platform implementation. Both are relevant to adoption, because eliminating an unwanted runtime does not eliminate the board's own interface requirements.
The repository also invites contributions for more boards, build systems and simple peripherals. That request identifies concrete work still needed. The existence of an ARM example does not establish every ARM board's support, and the existence of a RISC-V example does not establish a uniform peripheral interface across RISC-V devices.
Build-time specialization places additional responsibility on compilation, which is already my least pleasant part of Swift development. Here the tradeoff has a clear purpose. Work that would otherwise require runtime generic machinery moves into the compiler, allowing the resulting firmware to depend on a smaller execution environment.
The April examples make that design inspectable through complete board projects. Their most useful contribution is showing how specialized Swift code and a particular board's build requirements fit together. For firmware that can accept the language subset, that provides a way to retain familiar Swift abstractions while selecting the runtime facilities the target can support.