Blog / October 12, 2024 / 5 mins read / By Mahi Garg

Some vs Any in Swift

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, while any 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 use any 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, use some. 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, use any. 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.
Comments