Embedded Swift sigue evolucionando y ya no parece una variante limitada del lenguaje. Swift 6.4 amplía de forma importante el subconjunto de características disponibles para sistemas embebidos con soporte generalizado para existenciales any, errores no tipados, metatipos completos, parsing de números en coma flotante y operaciones que lanzan errores dentro del modelo de concurrencia. El objetivo continúa siendo el mismo: permitir escribir Swift expresivo y seguro en dispositivos con recursos muy limitados sin arrastrar los costes habituales del runtime completo.
Embedded Swift no es un lenguaje distinto. Es un subconjunto de Swift con un modelo de compilación pensado para microcontroladores, firmware, kernels y entornos bare metal. El compilador elimina mecanismos dinámicos que necesitan mucha infraestructura en tiempo de ejecución —como la reflexión general o la creación dinámica de metadatos de tipos— y especializa el código de forma más agresiva. Esto permite generar binarios pequeños, estáticos y con costes de ejecución y recursos mucho más visibles para el desarrollador.
Una de las piezas fundamentales de ese modelo es la especialización de genéricos. En Swift convencional, parte del comportamiento genérico puede resolverse en tiempo de ejecución utilizando metadatos. Embedded Swift intenta llevar esa resolución al tiempo de compilación: el compilador genera implementaciones concretas para cada combinación de tipos que realmente utiliza el programa. De esta forma puede eliminar código no utilizado con mucha más facilidad y evita incorporar infraestructura genérica innecesaria.
Swift 6.4 da un paso importante al permitir cualquier tipo existencial con any. Hasta ahora Embedded Swift restringía los existenciales principalmente a protocolos limitados a AnyObject, por lo que su uso estaba ligado a instancias de clases. La nueva versión permite trabajar también con valores y con el protocolo Any, acercando mucho más este código al que escribiríamos en una aplicación convencional.
protocol MetricValue {
var formatted: String { get }
}
struct Percentage: MetricValue {
let value: Int
var formatted: String {
"\(value)%"
}
}
let metric: any MetricValue = Percentage(value: 72)
print(metric.formatted)
Los existenciales siguen teniendo límites derivados del modelo de compilación de Embedded Swift. Un valor de tipo any Protocol oculta su tipo concreto, mientras que una función genérica necesita terminar especializándose para un tipo conocido. Por eso determinadas operaciones que intenten pasar un existencial a una API genérica continúan sin estar permitidas. No es una restricción arbitraria: evita volver a introducir precisamente el sistema dinámico de metadatos que Embedded Swift intenta eliminar.
La generalización de any desbloquea también otra característica muy habitual del lenguaje: throws sin especificar un tipo concreto de error. Embedded Swift ya permitía errores tipados mediante sintaxis como throws(MyError), pero Swift 6.4 permite utilizar el throws tradicional, cuyo error equivale conceptualmente a un valor de tipo any Error.
enum PacketError: Error {
case malformed
}
func decodePacket(_ bytes: [UInt8]) throws -> Int {
guard bytes.count >= 2 else {
throw PacketError.malformed
}
return Int(bytes[0]) << 8 | Int(bytes[1])
}
Esto es cómodo, pero no es gratis. Un error almacenado como any Error puede necesitar una asignación en el heap, mientras que los errores tipados ofrecen al compilador mucha más información. En firmware donde evitar asignaciones dinámicas sea un requisito, throws(MyError) sigue siendo una herramienta especialmente valiosa. Swift 6.4 no elimina esa diferencia: permite escoger entre una API más flexible y otra con un comportamiento de memoria más fácil de controlar.
Otro cambio especialmente relevante es el soporte completo para metatipos. Expresiones como SomeType.self existían en Embedded Swift de forma muy restringida, principalmente para proporcionar información de tipos a determinadas APIs genéricas. Swift 6.4 permite almacenar y utilizar metatipos como valores, incluyendo metatipos existenciales.
protocol Command {
init()
func execute()
}
struct ResetCommand: Command {
init() {}
func execute() {
print("Reset")
}
}
let commandType: any Command.Type = ResetCommand.self
let command: any Command = commandType.init()
command.execute()
Este cambio es más importante de lo que parece. Muchas arquitecturas Swift utilizan metatipos para registrar implementaciones, construir objetos a partir de tipos conocidos o mantener tablas de comandos. Hasta ahora, portar ese código a Embedded Swift podía obligar a rediseñarlo. Con Swift 6.4 una parte mucho mayor de estas abstracciones puede conservarse sin abandonar el modelo de compilación reducido.
La librería estándar también sigue ganando funcionalidad. Swift 6.4 incorpora en Embedded Swift el parsing de valores en coma flotante desde String, gracias a una reimplementación de estas APIs en Swift. Código tan cotidiano como convertir una cadena de texto en un Double deja de necesitar una implementación específica para el entorno embebido.
let rawVoltage = "3.28"
if let voltage = Double(rawVoltage) {
print("Voltaje: \(voltage)")
}
También mejora la concurrencia con errores. La librería de concurrencia de Embedded Swift pasa a soportar tareas que lanzan errores y grupos de tareas con errores. Esto permite reutilizar patrones de concurrencia estructurada mucho más cercanos a los de Swift convencional, incluso en sistemas donde el runtime disponible es deliberadamente pequeño.
enum ReadingError: Error {
case unavailable
}
func fetchReading(id: Int) async throws -> Int {
guard id >= 0 else {
throw ReadingError.unavailable
}
return id * 10
}
func collectReadings() async throws -> [Int] {
try await withThrowingTaskGroup(of: Int.self) { group in
for id in 0..<4 {
group.addTask {
try await fetchReading(id: id)
}
}
var values: [Int] = []
for try await value in group {
values.append(value)
}
return values
}
}
Que Embedded Swift soporte más características dinámicas no significa que todas tengan coste cero. El propio proyecto está intentando hacer más explícita esa relación entre abstracción y rendimiento. El grupo de diagnósticos PerformanceHints puede avisar sobre construcciones que introducen llamadas indirectas, metadatos adicionales u otros costes que pueden resultar relevantes en programas extremadamente sensibles al tamaño o al tiempo de ejecución. La idea es que el lenguaje permita utilizar estas herramientas cuando aportan valor, pero que el desarrollador pueda evitarlas cuando cada byte cuenta.
Esta evolución encaja con el trabajo realizado en Swift 6.3, donde Embedded Swift mejoró la interoperabilidad con C, la depuración y su modelo de enlazado. La interoperabilidad con C y C++ es especialmente importante en este ámbito porque permite consumir SDKs de fabricantes, controladores existentes y código de bajo nivel sin renunciar a escribir las capas superiores en Swift.
El resultado es una dirección cada vez más clara: Embedded Swift intenta conseguir que portar código existente requiera el menor número posible de cambios, en lugar de crear un lenguaje paralelo especializado para microcontroladores. Características como opcionales, protocolos, genéricos, seguridad de tipos y ahora una parte mucho mayor del modelo dinámico de Swift pueden convivir con binarios pequeños gracias a un modelo de compilación distinto.
Swift 6.4 ya está disponible para todos, aunque Embedded Swift continúa considerándose una tecnología experimental. Aun así, la diferencia entre “Swift para aplicaciones” y “Swift para firmware” se está estrechando rápidamente. Cada nueva versión reduce el número de decisiones arquitectónicas que hay que tomar únicamente por las restricciones del entorno, haciendo más realista compartir modelos, algoritmos y abstracciones entre software convencional y sistemas con recursos muy limitados.