Swift 5.7 introduced two powerful keywords, some
and any
, to work with protocols in generic programming. Although they serve different purposes, they both deal with protocols and types. If you’re unfamiliar with how to use them or what sets them apart, this blog post will clarify their differences and show you practical examples.
What are some and any?
Before Swift 5.7, protocols with associated types and generic constraints often made code complex to read. Swift introduced some
and any
as simpler, more powerful tools for abstracting over types that conform to protocols.
some
: Refers to an opaque type — a specific type that conforms to a protocol but the exact type isn’t revealed. It enforces that the type remains the same throughout the function or property.any
: Refers to an existential type — any type that conforms to a protocol, allowing multiple conforming types to be used interchangeably.
Let’s break these down further.
Using some for Opaque Types
When you use some
, Swift returns a concrete, specific type that conforms to a protocol, but you don’t know exactly which type it is. The compiler enforces that the same type is returned each time, ensuring consistency.
Example: some in Action
Consider a function that returns a value conforming to the Shape protocol:
protocol Shape {
func area() -> Double
}
struct Circle: Shape {
let radius: Double
func area() -> Double {
return .pi * radius * radius
}
}
struct Square: Shape {
let side: Double
func area() -> Double {
return side * side
}
}
func makeShape() -> some Shape {
return Circle(radius: 5)
}
let shape = makeShape()
print(shape.area()) // Output: 78.53981633974483
In this example, the makeShape()
function returns some Shape
, meaning it returns a specific type that conforms to Shape
— in this case, a Circle
. However, users of the function don’t need to know that the return type is Circle
; they only need to know it conforms to Shape
.
Key point: The type returned by some
must remain consistent. For example, you cannot return both Circle
and Square
from the same function using some Shape
.
func makeShape(basedOn value: Int) -> some Shape {
if value > 5 {
return Circle(radius: 5)
} else {
return Square(side: 4) // Error: Inconsistent return types
}
}
The above example will throw a compilation error because the return type changes between Circle
and Square
. Swift requires that the same concrete type is returned consistently when using some
.
Using any for Existential Types
When using any
, you’re telling Swift that the function can return any type that conforms to the protocol. Unlike some
, any
allows different types to be returned as long as they all conform to the specified protocol.
Example: any in Action
Let’s rewrite our previous example to use any
:
func makeShape(basedOn value: Int) -> any Shape {
if value > 5 {
return Circle(radius: 5)
} else {
return Square(side: 4)
}
}
let shape = makeShape(basedOn: 3)
print(shape.area()) // Output: 16.0
Now, the function works fine because any Shape
allows the function to return either Circle
or Square
, as long as both conform to Shape
. This is because any
does not care if the types are different, just that they conform to the Shape
protocol.
Key Differences between some and any
- Type Consistency:
some
enforces that the same concrete type is returned, whileany
allows multiple types to be returned. - Performance: Since
some
represents a specific type known at compile time, it’s generally more performant.any
adds a layer of abstraction, which may involve runtime type checking. - Use Case: Use
some
when you want type safety and consistency, and useany
when you need flexibility with different types.
When to use some vs any
- Use
some
when you need type consistency: If you need a function or property to always return a specific, but hidden, concrete type conforming to a protocol, usesome
. This can provide better performance and clarity.
Example: You have a method returning a particular type of Shape, such as Circle, and you want the user to interact with it via the Shape protocol.
- Use
any
when you need flexibility: If your function might return different types that all conform to the same protocol, useany
. This is ideal when you need dynamic behavior or don’t care about the concrete type.
Example: You have a method that can return either a Circle
or a Square
based on input, and both conform to the Shape
protocol.
Practical Example: A Drawing App
Suppose you’re building a drawing app where users can create different shapes.
With some:
func createShape() -> some Shape {
Circle(radius: 10)
}
let shape1 = createShape()
let shape2 = createShape()
print(type(of: shape1)) // Circle
print(type(of: shape2)) // Circle
With any:
func createRandomShape() -> any Shape {
if Bool.random() {
return Circle(radius: 5)
} else {
return Square(side: 4)
}
}
let shape3 = createRandomShape()
print(type(of: shape3)) // Could be Circle or Square
Here, createShape()
always returns a Circle
, whereas createRandomShape()
can return either a Circle
or a Square
, depending on random logic.
Conclusion
- Use
some
when you want to abstract away the type while ensuring it stays consistent. - Use
any
when you need the flexibility of working with different types that conform to the same protocol.