Swift en Windows: de funcionar a ser predecible
Arturo Rivas Arias
Durante años, hablar de Swift en Windows significaba demostrar que el compilador podía generar un ejecutable y que Foundation era capaz de resolver tareas reales fuera de los sistemas de Apple. Esa etapa ya está superada. Swift se utiliza en Windows sobre arquitecturas AMD64 y ARM64, tanto en herramientas de línea de comandos como en aplicaciones con interfaz gráfica, y para red algunos proyectos recurren directamente a URLSession mientras otros se integran con componentes escritos en C++.
El problema más importante ya no era conseguir que el código funcionase, sino lograr que lo hiciese siempre del mismo modo. Una herramienta podía compilar correctamente y fallar después en otra máquina, en integración continua o simplemente tras instalar un segundo toolchain. Durante el último año, buena parte del trabajo alrededor de Swift en Windows se ha concentrado en eliminar esas variables ocultas.
El problema de depender del Path
Una aplicación Swift no necesita únicamente su propio código. También utiliza el runtime del lenguaje, la librería estándar y, en función de sus imports, librerías como Foundation o Dispatch. En Windows, estos componentes se distribuyen principalmente como liberías DLL y el sistema debe localizarlos cuando arranca el proceso.
Hasta ahora, esa búsqueda podía depender del orden de las rutas incluidas en la variable de entorno Path. Si una máquina tenía instaladas varias versiones de Swift, una herramienta compilada con el toolchain X podía terminar cargando el runtime del toolchain Y. Ambas instalaciones podían parecer compatibles y, aun así, contener diferencias suficientes para provocar errores en el linkado, fallos durante la ejecución o comportamientos extraños difíciles de reproducir.
Esto es especialmente relevante porque Swift todavía no ofrece estabilidad de ABI en Windows. En los sistemas de Apple, una aplicación puede confiar en la ABI estable del sistema para comunicarse con una versión compatible del runtime. En Windows, el código debe estar vinculado a una versión concreta o distribuir junto a él los componentes que necesita.
El objetivo del trabajo reciente ha sido convertir esa relación implícita en una decisión explícita de compilación y despliegue.
Un nuevo sistema de compilación para el runtime
El primer paso fue modernizar la forma en la que se construye el propio runtime de Swift. Su sistema de compilación había acumulado durante una década numerosas reglas específicas de cada plataforma. Era funcional, pero resultaba difícil saber qué opciones se estaban aplicando realmente en cada configuración.
Ingenieros de The Browser Company, creadores de los navegadores Arc y Dia, Apple y la comunidad de Swift están trasladando esa infraestructura a un modelo moderno basado en CMake. El cambio puede parecer interno, pero tiene consecuencias directas para quienes desarrollan aplicaciones: una configuración reproducible reduce el riesgo de perder accidentalmente una opción del compilador, del enlazador o de LTO y permite comparar de forma fiable las distintas variantes del runtime.
También facilita la compilación cruzada. El nuevo sistema admite hosts Darwin y Windows y ya se utiliza, por ejemplo, para construir el runtime de Android desde Windows. No se trata simplemente de reemplazar unos scripts por otros, sino de expresar los requisitos de cada plataforma de una manera que pueda inspeccionarse y mantenerse.
Qué aporta el Experimental Swift SDK
Sobre esa base se ha creado el Experimental Swift SDK para Windows, una nueva organización del SDK que reúne en un solo artefacto todo lo necesario para ejecutar código Swift sobre el SDK de Windows:
- Las librerías fundamentales del lenguaje, como
Swift,_Concurrencyy_StringProcessing. - Las implementaciones multiplataforma de Foundation y Dispatch.
- XCTest y Swift Testing.
- Los module maps, APINotes y demás metadatos necesarios para integrar Swift con las APIs de la plataforma.
La diferencia no consiste únicamente en agrupar ficheros. El SDK describe de forma explícita cómo debe distribuirse el runtime: enlazado estáticamente, instalado en paralelo con otras versiones o incluido como una copia privada junto a la aplicación.
Conviene no perder de vista la palabra «experimental». El SDK está pensado para usarse y para validar aplicaciones reales, pero su estructura todavía puede cambiar. Tampoco debe confundirse con que Swift en Windows sea experimental en su conjunto: la web oficial ofrece instaladores estables para x86_64 y ARM64, además de imágenes de contenedor para Windows Server Core.
Una decisión importante sobre la evolución de librerías
El nuevo SDK deja de compilar el runtime de Windows con el modo de evolución de librerrías activado. En plataformas sin una ABI estable, habilitarlo no ofrecía el mismo contrato que en los sistemas de Apple. Añadía “indirecciones” y restringía la generación de código, pero no garantizaba que una aplicación pudiese utilizar indefinidamente cualquier versión futura del runtime.
Al desactivarlo, el código queda ligado de forma más precisa a la versión con la que se compiló. Esto reduce parte de la indirección asociada a la resiliencia y refleja mejor la realidad de la plataforma. La compatibilidad deja de plantearse como «cualquier runtime futuro respetará esta ABI» y pasa a expresarse como «esta aplicación utiliza esta versión concreta».
Esa decisión encaja con los dos mecanismos de despliegue que se están desarrollando: incluir el runtime en el binario o permitir que varias versiones convivan de forma controlada.
Enlazado estático: el runtime viaja con la herramienta
La novedad más visible es la posibilidad de enlazar estáticamente el runtime de Swift en Windows. Con el nuevo SDK, el compilador puede utilizar -static-stdlib para incorporar al resultado las librerías de Swift en lugar de buscarlas como DLL al iniciar el proceso.
Para una utilidad autocontenida, un formateador o una herramienta que debe ejecutarse igual en las máquinas de desarrollo y en CI, las ventajas son claras:
- La versión del runtime queda fijada durante la compilación.
- La ejecución deja de depender del orden de las toolchains en
Path. - El despliegue y la reversión de una versión resultan más predecibles.
- El linker puede aplicar eliminación de código muerto, LTO e inlining entre límites que antes pertenecían a librerías distintas.
El enlazado estático no es gratuito. El ejecutable aumenta de tamaño, varias aplicaciones no pueden compartir en memoria las mismas páginas de una DLL, los enlaces pueden tardar más y una corrección de seguridad exige recompilar y redistribuir cada herramienta. Además, -static-stdlib se refiere a las librerías de Swift; no convierte automáticamente todas las dependencias del sistema operativo o de terceros en código estático.
Por eso no existe una opción universal mejor. El enlazado estático encaja muy bien con herramientas de un único binario. El dinámico sigue siendo apropiado para aplicaciones grandes, ecosistemas de plugins o instalaciones donde interese actualizar una librería compartida de forma centralizada.
Por qué hay dos tipos de ficheros .lib
El modelo de enlace de Windows obliga a distinguir con claridad entre la implementación y la interfaz que ve el linker. Una librería dinámica incluye, por una parte, la DLL que contiene el código ejecutable y, por otra, una librería de importación que describe los símbolos disponibles.
La dificultad es que Windows utiliza la extensión .lib tanto para las librerías estáticas como para las librerías de importación. El Experimental Swift SDK adopta la convención de Microsoft para evitar colisiones:
libswiftCore.libcontiene la implementación estática.swiftCore.libes la librería de importación asociada a la DLL.
El nombre permite que ambas variantes convivan en el mismo SDK. El compilador, los metadatos del swiftmodule, las directivas de autoenlace y el enlazador mismo deben coincidir en la elección. Si uno de esos elementos cree que el runtime es estático y otro espera una DLL, el fallo puede aparecer mucho después y con un diagnóstico poco intuitivo.
El trabajo reciente también ha corregido cómo se marcan en la representación intermedia las funciones importadas o exportadas por una DLL. Antes se asumía en varios puntos que el runtime siempre era dinámico y el enlazador tenía que compensar algunas discrepancias mediante indirecciones adicionales. Hacer explícito el origen de cada símbolo elimina esa fragilidad.
El propio compilador sirve como prueba
Una plataforma alcanza un nivel superior de madurez cuando es capaz de construir sus propias herramientas. El proceso de self-hosting fuerza a Swift a ejercitar las rutas de compilación, optimización, distribución y descubrimiento del runtime que también utilizan las aplicaciones reales.
El swift-driver para Windows ya puede construirse como un ejecutable enlazado estáticamente utilizando los mismos mecanismos expuestos por el nuevo SDK. De este modo, ni siquiera el controlador del compilador depende de que una DLL compatible aparezca antes que otra en Path.
The Browser Company también distribuye variantes Asserts y NoAsserts de su toolchain. La primera conserva las aserciones internas del compilador para detectar antes posibles errores; la segunda prioriza la velocidad y ha resultado aproximadamente un 20 % más rápida en las pruebas. Es una distribución propia, no una garantía aplicable automáticamente a todos los instaladores oficiales. En la misma línea, sus compilaciones con mimalloc han mostrado una mejora aproximada del 4 % en ciertas cargas de compilación.
Versiones paralelas del runtime
El enlazado estático no cubre todos los escenarios. Para aplicaciones que prefieren DLL compartidas, Windows dispone del modelo side-by-side o SxS. Este mecanismo permite instalar varias versiones de una librería y utilizar manifiestos para indicar cuál debe cargar cada aplicación.
El runtime compartido del Experimental Swift SDK ya incorpora el manifiesto necesario para avanzar en esa dirección. Esto permite que distintas aplicaciones utilicen versiones diferentes sin obligar a migrarlas todas al mismo tiempo y sin depender de una búsqueda global basada únicamente en Path.
La combinación es coherente con la ausencia actual de estabilidad ABI: en lugar de fingir que cualquier versión es intercambiable, cada aplicación declara la implementación con la que es compatible.
SwiftFormat y SwiftLint como validación real
Estas mejoras ya se están probando con herramientas conocidas. SwiftFormat puede distribuirse con el runtime enlazado estáticamente, lo que evita que el formateador de un proyecto cargue por accidente las DLL de otra toolchain. La misma compilación puede utilizarse en los equipos del grupo y en integración continua con un comportamiento consistente.
SwiftLint ha dado otro paso relevante: ahora cuenta con soporte de primer nivel para Windows. No solo compila con los nuevos SDK, sino que ejecuta su batería de pruebas y produce los mismos diagnósticos para la misma entrada en macOS y Windows. Esto permite aplicar las mismas reglas de estilo en una organización sin mantener bifurcaciones específicas por sistema operativo.
La validación mediante herramientas reales es más valiosa que una demostración aislada. Un formateador o un linter recorre gran cantidad de código, se instala junto a diferentes versiones de Swift y suele ejecutarse tanto localmente como en CI. Es precisamente el entorno donde los errores de selección del runtime resultan más probables.
Una experiencia de desarrollo mucho más completa
La infraestructura de despliegue no ha avanzado sola. Desde Swift 6.2, la extensión oficial de Swift para Visual Studio Code se distribuye verificada por Swift.org e incluye indexación en segundo plano, depuración mediante LLDB, navegación por objetivos y dependencias de Swift Package Manager y previsualización en vivo de documentación DocC.
La instalación estable actual también es bastante más sencilla que hace unos años. En Windows 11 puede realizarse con WinGet después de instalar el SDK de Windows y las herramientas de C++ de Visual Studio:
winget install --id Microsoft.VisualStudio.2022.Community --exact --force `
--custom "--add Microsoft.VisualStudio.Component.Windows11SDK.22621 --add Microsoft.VisualStudio.Component.VC.Tools.x86.x64 --add Microsoft.VisualStudio.Component.VC.Tools.ARM64"
winget install --id Swift.Toolchain --exact
Después se puede crear y ejecutar un paquete sin ninguna estructura especial:
mkdir ClimateSummary
cd ClimateSummary
swift package init --name ClimateSummary --type executable
swift run ClimateSummary
Un ejemplo de código realmente multiplataforma
La unificación de Foundation iniciada con Swift 6 permite compartir APIs como JSONDecoder, URL, Calendar, FileManager o ProcessInfo entre los sistemas de Apple, Linux y Windows. A modo de ejemplo, una herramienta sencilla para gestionar mediciones de temperatura puede utilizar exactamente el mismo núcleo en todas esas plataformas:
import Foundation
struct Measurement: Decodable {
let city: String
let temperature: Double
}
struct ClimateReport {
let measurements: [Measurement]
var averageTemperature: Double {
guard !measurements.isEmpty else { return 0 }
return measurements.map(\.temperature).reduce(0, +)
/ Double(measurements.count)
}
}
let input = """
[
{ "city": "León", "temperature": 17.5 },
{ "city": "Getafe", "temperature": 24.0 },
{ "city": "Santander", "temperature": 20.5 }
]
""".data(using: .utf8)!
let measurements = try JSONDecoder().decode(
[Measurement].self,
from: input
)
let report = ClimateReport(measurements: measurements)
print("Media: \(report.averageTemperature.formatted()) °C")
#if os(Windows)
print("Calculado en Windows")
#elseif os(macOS)
print("Calculado en macOS")
#endif
El bloque condicional debería reservarse para la integración con cada plataforma. Los modelos, el parseo y las reglas de negocio permanecen compartidos. La interfaz de usuario seguirá necesitando una tecnología propia —SwiftUI en los sistemas de Apple o las APIs de Windows correspondientes—, pero la parte reutilizable puede vivir en un paquete Swift común.
Swift Testing también está incluido en las toolchains modernas y funciona en Windows. El mismo cálculo puede comprobarse sin añadir una dependencia externa:
import Testing
@testable import ClimateSummary
@Test("Calcula la temperatura media")
func averageTemperature() {
let report = ClimateReport(measurements: [
Measurement(city: "León", temperature: 10),
Measurement(city: "Getafe", temperature: 20)
])
#expect(report.averageTemperature == 15)
}
Mejor interoperabilidad con las APIs de Windows
Otra mejora en desarrollo afecta al Clang Importer. Muchas funciones de Win32 tienen dos variantes: una terminada en A, que usa cadenas ANSI, y otra en W, que usa cadenas Unicode codificadas en UTF-16. En C, la macro UNICODE permite utilizar un nombre sin sufijo, como CreateFile, que se resuelve automáticamente a la variante adecuada.
Swift no reproducía ese alias controlado por macros y obligaba a llamar de forma explícita a CreateFileW. La opción experimental -enable-experimental-feature ImportMacroAliases permite que el importador exponga el nombre sin sufijo y lo resuelva como lo haría C. Esto acerca el código Swift a la documentación convencional de Win32 y reduce detalles accidentales al portar ejemplos, aunque todavía es una función voluntaria y experimental.
La interoperabilidad con C y C++ continúa siendo esencial. Swift no proporciona en Windows los frameworks de interfaz de Apple, pero puede acceder a gran parte de la plataforma mediante sus APIs C, C++ y COM. El objetivo no es trasladar una aplicación SwiftUI sin cambios, sino permitir que paquetes, herramientas y componentes nativos compartan un lenguaje seguro sin renunciar a la integración con el sistema.
Seguridad de la cadena de suministro
El proceso de construcción de la toolchain genera ahora una SBOM (Software Bill of Materials), un inventario de los componentes utilizados. Este documento facilita auditar la procedencia del compilador y sus dependencias, detectar versiones afectadas por vulnerabilidades y evaluar la adopción en organizaciones con requisitos de cumplimiento.
No cambia la sintaxis de Swift, pero sí elimina otra barrera para utilizarlo en entornos profesionales. La madurez de una plataforma también se mide por su capacidad de explicar qué contiene una herramienta y cómo fue construida.
Lo que todavía queda por mejorar
Swift en Windows ha avanzado mucho, pero no todos los problemas están resueltos. La depuración de código debe acercarse todavía más a la experiencia disponible en los sistemas de Apple, se está estudiando un servidor público de símbolos para obtener trazas útiles sin gestionar manualmente los ficheros de depuración y continúa el trabajo sobre problemas de rendimiento específicos de Windows.
La diferencia es que esas mejoras pendientes se apoyan ahora en una base mucho más sólida y sobretodo explícita. El SDK describe qué librerías forman parte del entorno, el sistema de compilación reproduce sus variantes y el despliegue puede elegir entre incorporar el runtime o seleccionar una versión compartida concreta.
Swift en Windows ya había demostrado que podía ejecutar software de producción. El último año ha servido para que ese software dependa cada vez menos de coincidencias del entorno y más de decisiones declaradas por el proyecto. Esa previsibilidad es la que convierte el soporte de una plataforma en una opción realmente mantenible.