Las decisiones que definieron el lenguaje
Un recorrido por las propuestas históricas que dieron forma a Swift: la gran poda de la versión 3, los rechazos que fijaron su carácter, las apuestas de largo plazo como la concurrencia y los tipos opacos, y lo que sigue deliberadamente fuera del lenguaje.
Un lenguaje maduro es la suma de lo que aceptó y de lo que se negó a aceptar, y en Swift ambas listas están publicadas con su razonamiento. Repasarlas no es arqueología: es la única forma de entender por qué ciertas construcciones que existen en casi todos los lenguajes emparentados no existen aquí, por qué otras llegaron con una forma extraña, y por qué el lenguaje que escribes hoy tiene exactamente esta silueta y no otra. Las decisiones que más lo definieron no fueron las adiciones espectaculares sino tres cosas menos vistosas: una poda temprana y dolorosa, una serie de rechazos sostenidos contra ideas populares, y unas pocas apuestas de largo plazo cuyo diseño se planeó años antes de que se pudiera usar.
- Explicar qué se eliminó en la gran poda de Swift 3 y con qué criterio se eliminó.
- Reconstruir el razonamiento de rechazos históricos que fijaron el carácter del lenguaje.
- Rastrear cómo una apuesta de largo plazo se despliega en propuestas encadenadas durante años.
- Argumentar qué sigue fuera del lenguaje y por qué su ausencia es una decisión y no un olvido.
La poda de Swift 3
La versión 3, publicada en 2016, es la única que rompió el ecosistema a conciencia y a gran escala. Fue posible porque llegó antes de la estabilidad de ABI y antes de que Swift se usara masivamente en producción, y el equipo la trató como la última oportunidad de corregir errores de juventud.
Se eliminaron los incrementos ++ y -- porque duplicaban a += 1 sin aportar claridad y arrastraban una semántica de prefijo y sufijo confusa. Se eliminó el bucle al estilo de C porque los rangos y stride cubrían el mismo terreno con menos formas de equivocarse. Se eliminaron los parámetros declarados como variables mutables dentro de la función y la sintaxis de currificación en declaraciones, ambas por ser atajos que complicaban el modelo mental sin resolver nada que no se pudiera escribir de otro modo.
// Swift 2, todo esto desaparecio en Swift 3
for var i = 0; i < 10; i++ { usar(i) }
// Swift 3 en adelante
for i in 0..<10 { usar(i) }
for i in stride(from: 0, to: 10, by: 2) { usar(i) }
También desapareció la aplicación de tuplas, la posibilidad de llamar a una función pasándole una tupla cuyos elementos encajaban con sus parámetros. Era una comodidad elegante y su eliminación se justificó por algo que no se ve desde fuera: mantener la equivalencia entre listas de parámetros y tuplas complicaba enormemente el sistema de tipos y la resolución de sobrecargas. Aquella limpieza tuvo secuelas —una propuesta posterior separó del todo los argumentos de función de las tuplas y provocó fricciones reales con las clausuras que hubo que suavizar en la versión siguiente—, y es un buen recordatorio de que incluso las podas bien argumentadas dejan cicatriz.
Pero el cambio de mayor alcance no fue una eliminación sino un renombrado: las guías de diseño de API y la traducción sistemática de las interfaces heredadas de Objective-C. De golpe, miles de métodos cambiaron de nombre siguiendo reglas explícitas —omitir palabras redundantes con el tipo, nombrar según el papel y no según la clase, usar formas verbales para las mutaciones y participios para las variantes que devuelven copia—. Ese trabajo convirtió una convención estética en una regla enseñable, y es la razón por la que hoy reconoces código idiomático de Swift sin poder explicar del todo por qué.
Todo lo que se podó fue posible porque aún no existía estabilidad de ABI ni compromiso de compatibilidad de fuente. Cuando leas una propuesta actual que parece aceptar una solución más fea de lo necesario, la explicación suele ser que la elegante exigía una poda que ya no está disponible.
Rechazos que fijaron el carácter
Hay tres rechazos que explican más sobre Swift que muchas aceptaciones.
Exigir self para acceder a miembros de instancia. Se propuso muy pronto y con argumentos serios: haría explícita la captura en clausuras y eliminaría el sombreado accidental de nombres. Se rechazó porque el coste en verbosidad recaía sobre todo el código escrito para prevenir un error poco frecuente, y porque el compilador ya exige self justo donde el peligro existe, dentro de clausuras que escapan. Es la doctrina de la revelación progresiva aplicada con rigor: la fricción va donde está el riesgo, no repartida por igual.
El tipo Result durante años. La comunidad lo pidió repetidamente y se aplazó una y otra vez con el argumento de que el modelo de errores del lenguaje ya cubría el caso y que la asincronía tendría su propia solución. Solo se aceptó en Swift 5, cuando quedó claro que las retrollamadas iban a convivir mucho tiempo con el nuevo modelo. La lección es sobre secuenciación: la misma propuesta puede ser incorrecta en un momento y correcta cinco años después sin que cambie una línea de su diseño.
Los errores tipados en el primer intento. Se rechazaron en la era de Swift 2 con un razonamiento que hoy se lee como profético: obligar a enumerar los errores posibles en la firma acopla a quien llama con detalles internos y hace que cada cambio interno rompa la interfaz. Se aceptaron finalmente para Swift 6, pero con una motivación completamente distinta: entornos empotrados y código genérico sobre errores, no la ergonomía general. La forma volvió; el argumento que la trajo era otro.
Un cuarto caso menos citado completa el cuadro: los comportamientos de propiedades, propuestos en 2016 para permitir que un atributo definiera el almacenamiento y el acceso de una propiedad. Se aplazaron por complejidad y por interacciones sin resolver con la inicialización. Tres años después la misma necesidad volvió con otro diseño, más restringido y basado en un tipo genérico en lugar de una construcción del compilador, y esa versión sí se aceptó porque cabía dentro del sistema de tipos existente en vez de ampliarlo. Las tres historias comparten moraleja: en Swift, una idea rechazada no está muerta, está esperando una formulación que quepa en el lenguaje.
Junto a las propuestas conviven los manifiestos y documentos de visión: el de genéricos, el de propiedad, el de concurrencia, el de macros. Describen el destino completo de un área con años de antelación y explican qué está fuera de alcance y por qué. Son la mejor lectura disponible para entender no lo que Swift es, sino hacia dónde va.
flowchart LR A[Swift 3 en 2016: poda y renombrado] --> B[Swift 5 en 2019: estabilidad de ABI] B --> C[Swift 5.1: some y property wrappers] C --> D[Swift 5.5: async await y actores] D --> E[Swift 5.6 y 5.7: any y existenciales abiertos] E --> F[Swift 5.9: macros y tipos no copiables] F --> G[Swift 6: modo de lenguaje y errores tipados] style A fill:#f38ba8,color:#11111b style B fill:#f9e2af,color:#11111b style G fill:#a6e3a1,color:#11111b
Las apuestas de largo plazo
Las adiciones grandes de Swift no llegaron como propuestas sueltas sino como programas desplegados durante años, y verlas así explica su forma.
La concurrencia estructurada es el caso ejemplar. Empezó con un manifiesto que describía el destino completo antes de proponer nada, siguió con la propuesta de async y await que solo aportaba sintaxis y modelo de suspensión, continuó con las tareas estructuradas, después con los actores para dar aislamiento a los datos mutables, luego con Sendable para que el compilador pudiera verificar qué cruza fronteras, y terminó con las propuestas de migración incremental que permitieron activar la comprobación estricta por módulos. Ninguna pieza tenía sentido aislada; cada una era ininteligible sin la siguiente.
El otro programa es el de la abstracción sobre tipos. La biblioteca estándar arrastraba una asimetría incómoda: los protocolos con tipos asociados no podían usarse como tipos, y los que sí podían pagaban un coste dinámico invisible. La respuesta fue gradual y en un orden que ahora parece obvio: primero some para los retornos opacos, después any para nombrar explícitamente lo que antes era implícito, luego la apertura de existenciales para permitir protocolos con tipos asociados, y por último los tipos asociados primarios para poder restringirlos.
// Antes: el mismo nombre para dos cosas radicalmente distintas
func hacer() -> Coleccionable // existencial con coste dinamico
// Despues: la distincion es visible en el punto de uso
func hacer() -> some Coleccionable // un tipo concreto oculto
func hacer() -> any Coleccionable // una caja con despacho dinamico
El patrón se repite: primero se nombra el problema en un documento de visión, luego se introduce la pieza que no rompe nada, después la que exige decisiones al programador y al final la que permite migrar el código antiguo. Invertir ese orden habría partido el ecosistema en cada paso.
Un tercer programa, más reciente, aplica la misma disciplina al modelo de propiedad y a la memoria: primero los modificadores que hacen explícita la transferencia y el préstamo de un valor, después los tipos que no se pueden copiar, y a continuación las piezas que permiten escribir código genérico sobre esa distinción. Su motor no es la ergonomía sino la ambición de que Swift sirva donde antes no servía —sistemas empotrados, código sin asignación dinámica, sustitución de C— y por eso sus propuestas se leen distinto: hablan de garantías de rendimiento con la misma seriedad con la que otras hablan de legibilidad.
// Tres decadas de diseno de lenguajes condensadas en dos palabras clave
struct Descriptor: ~Copyable {
private let fd: Int32
consuming func cerrar() { close(fd) }
borrowing func leer() -> Datos { sistema.leer(fd) }
}
// El tipo no se puede duplicar, el cierre consume el valor
// y el compilador garantiza que nadie lo usa despues.
Lo que sigue fuera
Un catálogo breve de ausencias notables, cada una con su motivo documentado.
Tipos de orden superior. Poder abstraer sobre constructores de tipos, como hacen los lenguajes funcionales con las mónadas, está reconocido en el manifiesto de genéricos como deseable y sigue sin proponerse en serio. El motivo es de coste: cambia el sistema de tipos entero y su interacción con la resolución de sobrecargas y la especialización no está resuelta.
Herencia múltiple de implementación. Nunca se ha considerado. Los protocolos con extensiones por defecto cubren buena parte del terreno con reglas de resolución mucho más simples.
Un sistema de efectos general. Swift tiene efectos concretos —lanzar, suspender— con sintaxis dedicada, pero no un mecanismo para declarar efectos propios. Aparece una y otra vez en los foros y choca siempre con lo mismo: un sistema general exigiría polimorfismo sobre efectos en toda la biblioteca estándar.
Macros arbitrarias sobre la sintaxis. Las macros existen desde Swift 5.9, pero deliberadamente acotadas por roles y ejecutadas en un proceso aparte, sin poder ver ni modificar el resto del programa. La alternativa sin restricciones se descartó porque destruiría las garantías de comprensión local del código.
Excepciones al estilo de C++ o Java. El modelo de errores de Swift exige marcar en la firma que una función puede fallar y en el punto de llamada que estás asumiendo ese fallo. Las excepciones invisibles se descartaron desde el primer diseño porque hacen que cualquier línea pueda alterar el flujo sin dejar rastro en el texto, lo que resulta incompatible con el principio de que el código diga en el punto de uso lo que hace.
Ninguna de estas ausencias está cerrada para siempre, y ese es justo el matiz que distingue una decisión razonada de un dogma. Los errores tipados volvieron ocho años después porque apareció un contexto —Swift en sistemas empotrados— que cambió la relación entre coste y beneficio. Cualquiera de las cuatro puede volver el día que alguien traiga una formulación que quepa en el lenguaje actual o un dominio nuevo que justifique su precio. Lo que no volverá es una versión que rompa el ecosistema entero para acomodarlas.
Una sola poda
Swift 3 gastó todo el crédito de ruptura que el lenguaje tenía. Todo lo posterior se diseña con esa restricción.
El momento es parte del diseño
Result y los errores tipados fueron rechazados y luego aceptados sin cambiar de forma. Cambió el contexto.
Programas, no propuestas
La concurrencia y la abstracción de tipos llegaron como secuencias planificadas de propuestas encadenadas.
Las ausencias están documentadas
Los tipos de orden superior o un sistema de efectos general no faltan por olvido, sino por coste declarado.
Al estudiar la historia de un lenguaje la tentación es leerla como acumulación, una lista de adquisiciones donde cada versión suma capacidades; pero la historia de Swift se lee mucho mejor como una política sostenida de escasez, y esa diferencia de lectura tiene consecuencias prácticas. Cada construcción que un lenguaje admite no solo ocupa sintaxis: multiplica el número de interacciones posibles con todas las demás, y ese producto crece de forma que ninguna documentación alcanza a cubrir. Por eso la pregunta que un diseñador experimentado se hace ante una propuesta no es si la funcionalidad es útil —casi siempre lo es para alguien— sino cuántas conversaciones nuevas obliga a mantener para siempre: cómo interactúa con la inferencia, con la resolución de sobrecargas, con la resiliencia de bibliotecas, con los diagnósticos, con lo que hay que enseñar el primer día. Las tres decisiones estructurales de Swift responden todas a esa aritmética. La poda de la versión 3 fue el reconocimiento de que la deuda de diseño, a diferencia de la deuda de implementación, se vuelve impagable en cuanto hay compromisos binarios, de modo que la única ventana para saldarla es antes de tenerlos. Los rechazos sostenidos frente a ideas populares muestran que la popularidad de una petición mide su frecuencia, no su valor neto, y que un proceso que respondiera a la frecuencia produciría un lenguaje que es la unión de los deseos de todos, es decir, ningún lenguaje. Y el despliegue de las apuestas grandes en secuencias planificadas revela la asimetría más útil de todas: la complejidad que se introduce en el orden correcto puede enseñarse por capas y adoptarse gradualmente, mientras que la misma complejidad introducida de golpe es indistinguible de la arbitrariedad. La transferencia va bastante más allá del diseño de lenguajes: cualquier sistema con dependientes —una API, un esquema, un marco interno— acumula su deuda estructural en el mismo sitio y por la misma vía, que es aceptar peticiones individualmente razonables sin llevar la cuenta del producto de interacciones que generan. La disciplina que hace falta no es decir que no más veces, sino escribir cada no con su razón, para que el próximo sí llegue en el orden correcto.
Swift 3 podó lo que ya no podría podarse nunca más y fijó las guías de nombrado. Los rechazos de self obligatorio, de Result y de los errores tipados en su primer intento definieron el carácter del lenguaje tanto como cualquier adición. Las apuestas grandes se desplegaron como secuencias planificadas, y las ausencias notables están documentadas con su motivo en manifiestos y propuestas rechazadas.
- Elige una eliminación de Swift 3 y reescribe un fragmento antiguo con la forma actual, anotando qué se ganó.
- Lee el rechazo de exigir
selfy contrasta su argumento con la regla que sí aplica dentro de clausuras que escapan. - Reconstruye la cadena completa de propuestas de la concurrencia y ordénalas por dependencia conceptual.
- Explica en un párrafo por qué
sometuvo que llegar antes queanyy no al revés. - Toma una ausencia del último apartado y redacta la objeción principal que tendría tu propuesta para reintroducirla.