En resumen: dos comportamientos que ya eran distintos se habían agrupado bajo una sola sintaxis, y Swift ha hecho visible esa diferencia. some View, any Error, some Collection. Antes se usaba el nombre del protocolo directamente en posiciones de tipo, pero ahora el compilador exige any. ¿Qué ha cambiado?
La respuesta estática y dinámica al polimorfismo, anticipada en el artículo sobre genéricos, es precisamente esta distinción. En la tercera entrega de la serie intermedia, ponemos en orden some y any.
Origen del problema — los protocolos tienen dos caras en posiciones de tipo
Los protocolos son originalmente un lenguaje de restricciones. Expresan capacidades, como un tipo que adopta Comparable. Sin embargo, cuando el nombre de un protocolo se usa como variable o tipo de retorno, su naturaleza cambia.
let shapes: [Shape] = [Circle(), Square(), Triangle()]
Este array mezcla tipos diferentes. Para hacerlo posible, el compilador mete cada valor en una caja: una caja etiquetada como «algo que cumple Shape». Es un existential type. El tipo concreto del interior solo se conoce en tiempo de ejecución, y las llamadas a métodos se convierten en llamadas indirectas que abren la caja y buscan la implementación real.
El problema es que esa caja no es gratuita. Como hay que guardar valores de tamaños distintos en una caja uniforme, existe un formato de existential container independiente; los valores grandes se colocan en el heap y la caja solo conserva un puntero. Las llamadas pasan por una tabla de funciones llamada witness table, lo que produce despacho dinámico y bloquea gran parte de las optimizaciones del compilador, como el inlining y la especialización. También hay limitaciones funcionales: como el tipo concreto se ha borrado (type erasure), resulta difícil responder si dos Shapes son del mismo tipo.
La sintaxis antigua ocultaba este coste. func draw(shape: Shape) crea una caja, mientras que func draw<S: Shape>(shape: S) funciona como genérico sin ella. Dos fragmentos de código aparentemente similares tienen características de rendimiento completamente distintas, pero la sintaxis escondía la diferencia. Esa es exactamente la razón por la que la propuesta Swift Evolution SE-0335 introdujo la palabra clave any: escribir que hay una caja allí donde se crea. Es otro caso en el que la filosofía de no ocultar los costes, vista en la primera parte, desemboca en un cambio de sintaxis.
some — ocultar sin caja
Si any es una caja que puede contener cualquier cosa, some es la herramienta opuesta. some Shape significa que el tipo concreto está fijado a uno, pero su nombre no se revela. Por eso se denomina opaque type.
func makeShape() -> some Shape {
Circle(radius: 10) // siempre Circle devuelve un único tipo
}
El llamador no conoce el nombre Circle, pero el compilador sí. Por eso no hay caja ni llamada indirecta. El despacho estático y las optimizaciones siguen disponibles, por lo que en la práctica recibe un trato similar al de un genérico. El precio es la flexibilidad: una función que devuelve some Shape debe devolver el mismo tipo concreto en todas las rutas de retorno. Devolver Circle o Square según una condición produce un error de compilación.
El var body: some View de SwiftUI es el uso más representativo de esta sintaxis. El tipo que body devuelve realmente es un monstruo como VStack<TupleView<(Text, Image)>>, y no se puede ni se quiere escribir en la firma. some View resuelve el problema ocultando solo el nombre, mientras el compilador sigue conociendo el tipo concreto, sin renunciar al rendimiento. Esta es la verdadera naturaleza de la sintaxis que vimos en Progressive Disclosure como ejemplo de «complejidad que los principiantes no necesitan conocer».
También conviene conocer some en la posición de parámetro. func draw(shape: some Shape) es una forma abreviada de func draw<S: Shape>(shape: S) (SE-0341). Es una sintaxis ligera para usar genéricos cuando el nombre del parámetro de tipo no se necesita en el cuerpo.
Criterio de elección — some por defecto, any cuando haya un motivo
La diferencia puede resumirse así: some fija un tipo en tiempo de compilación (despacho estático, optimización posible, relaciones de tipo preservadas); any permite cualquier tipo en tiempo de ejecución (despacho dinámico, coste de la caja, borrado de tipos).
La regla práctica es clara: some (o genéricos) por defecto, y any solo cuando exista una razón real para mezclar varios tipos.
Hay aproximadamente tres usos legítimos de any. Primero, las colecciones heterogéneas. Guardar tipos distintos en un mismo array, como en [any Shape], es imposible sin una caja. Segundo, las propiedades almacenadas cuyo tipo se decide en tiempo de ejecución, como var strategy: any PaymentStrategy, donde se conecta una implementación diferente según la configuración. Las propiedades de protocolo del patrón Strategy y de la inyección de dependencias suelen estar aquí. Tercero, las funciones cuyo tipo de retorno cambia según una condición, el caso que some no permite.
Dicho de otro modo, si solo quieres que un parámetro de función acepte cualquier tipo que cumpla este protocolo, la respuesta es some. En cada llamada, el tipo queda fijado. La guía del equipo de Swift apunta en la misma dirección: convertirlo en any solo cuando haya que mezclarlo o almacenarlo en una colección.
No hace falta exagerar ni ignorar la diferencia de rendimiento. En código que procesa unos pocos eventos de UI, el coste de any es irrelevante. Pero dentro de un bucle que se ejecuta decenas de miles de veces por segundo, el coste de la caja y las optimizaciones bloqueadas pueden producir una diferencia medible. Como siempre, el criterio es medir; usar some por defecto reduce incluso lo que hay que medir.
Interpretar los mensajes de error — qué te está diciendo «añade any»
Cuando entiendes esta distinción, los mensajes del compilador que antes memorizabas para pasar de largo se vuelven comprensibles.
«Use of protocol ‘X’ as a type must be written ‘any X’» exige reconocer mediante la sintaxis que usar un protocolo como tipo crea una caja. Antes de añadir any mecánicamente, conviene preguntar si esa posición realmente necesita una caja (o si puede resolverse con some o genéricos). Esa es la forma correcta de interpretar este error.
«Protocol ‘X’ can only be used as a generic constraint» es el famoso error de Swift antiguo, que aparecía al usar como tipo un protocolo con associatedtype o Self. Significaba que no se podía definir el formato de la caja porque se desconocía el tipo asociado. Hoy el lenguaje permite mucho más gracias a SE-0309 y primary associated types, pero para entenderlo bien hay que tratar los tipos asociados. Ese será el tema del próximo artículo.
Resumen
- Usar un protocolo en una posición de tipo crea un existential type (una caja), junto con despacho dinámico, coste del contenedor y borrado de tipos. any es la etiqueta honesta de esa caja.
- some hace lo contrario: fija un tipo concreto y oculta solo su nombre. Al no haber caja, el rendimiento es como el de los genéricos; some View de SwiftUI es el ejemplo representativo.
- Usa some (genéricos) por defecto, y reserva any para lugares donde se mezclen realmente varios tipos: colecciones heterogéneas, almacenamiento decidido en tiempo de ejecución y retornos condicionales.
- Lee la exigencia de any del compilador como una señal para reconocer el coste de la caja y, antes de añadirlo sin más, comprueba si el lugar puede resolverse con some.
El próximo artículo trata, como se anunció, associatedtype: el origen del error «generic constraint», qué significa que un protocolo contenga un hueco de tipo y los primary associated types.

![Imagen de portada de [Swift intermedio #3] some frente a any y el coste de los existenciales](/assets/images/posts/a880d464-6c55-4a06-bf69-b6f45434a102/1.jpg)