If you have ever written a single line of SwiftUI, you have seen this code:

var body: some View {
    Text("Hello, World!")
}

That tiny word—some—represents one of the most powerful and complex features introduced in modern Swift: Opaque Return Types.

But what exactly does some do? Why can’t we just return View? And when should you use opaque types in your own non-UI Swift code? Let’s break down the what, why, where, and the common violations.


What is an Opaque Type?

An opaque type allows a function or property to hide its exact return type from the caller, while still allowing the compiler to know exactly what that type is.

When you write -> some Protocol, you are telling the compiler: “I am going to return a specific, concrete type that conforms to this protocol, but I am not going to tell the caller what it is.”

The Problem it Solves (Why we need it)

Before opaque types, if you wanted to hide implementation details, you returned a standard protocol.

// Swift 5.0 and earlier
func makeShape() -> Shape {
    return Circle()
}

However, returning a bare protocol (now written as any Shape in modern Swift) creates an Existential Type. Existentials are stored in memory boxes, require dynamic dispatch (which is slow), and fundamentally break if the protocol has an associatedtype or Self requirement (like View, Equatable, or Collection).

Opaque types solve this. With some Shape, the compiler looks at the return Circle() line and secretly rewrites the function signature at compile time.

  • To the caller: It is just a Shape.
  • To the compiler: It is exactly a Circle, allowing for highly optimized, statically dispatched code.

Opaque Return Types is a feature added in Swift 5.1. It comes to fix a fundamental problem of the usage of protocols and the design of Swift APIs. It can be used to return some value for function/method , and property without revealing the concrete type of the value to client that calls the API.

“Used when we want to hide the type of the return value. It is just opposite to Generics. Opaque type are different from Protocol because in Opaque type compiler will know the type but in Protocol compiler does not know which type it is. Example if we assign the type to another type it will return a error but it will not return in case of Protocol”

We all are well aware about using protocol as a return type. Using Protocol as return type means, we are providing protocol as the return type of the function. Like in below example, we are returning Shape protocol type in the function getShapeType() and later we can typecast that protocol type according to our usage as done at line number.

protocol Shape {
  func makeShape() -> Int
}

struct Circle: Shape {
    let name = "Circle"
    func makeShape() -> Int {
        return 1
    }
}

func getShapeType() -> Shape {
  let shape = Circle()
  return shape
}

if let circle = getShapeType() as? Circle {
  print(circle.name)
}

Now, let’s imagine a scenario where we are not knowing the datatype of makeShape() method used in protocol Shape. Let’s start making things more Generic. Yes, Correct I am talking about associatedType now. Once, our protocol starts having associatedType, compiler will starts providing error at compilation time while returning protocol type. The main reason behind this error is that, we have made our protocol Generic so it doesn’t have their own dataType and it will follow the dataType from the conforming struct, classes or enums.

protocol Shape {
    associatedtype T
    func makeShape() -> T
}

struct Circle: Shape {
    typealias T = Int
    let name = "Circle"
    func makeShape() -> Int {
        return 1
    }
}

func getShapeType() -> Shape { // Error: Use of protocol 'Shape' as a type must be written 'some Shape' or 'any Shape'
    let shape = Circle()
    return shape
}

if let circle = getShapeType() as? Circle {
    print(circle.name)
}

Now, Let’s fix this issue. Opaque Type comes in feature to fix this issue. Opaque type doesn’t know the internal datatype of the protocol rather it uses some keyword to inform compiler that return type could be of that particular protocol type. Let’s solve this interesting problem using Opaque Type.

protocol Shape { 
    associatedtype T
    func makeShape() -> T
}
class Circle: Shape {
    typealias T = Int
    var name = "circle"
    func makeShape() -> Int {
        return 1
    }
}
class Triangle: Shape {
    typealias T = Double
    var name = "triangle"
    func makeShape() -> Double {
        return 1.0
    }
}
func getShapeType () -> some Shape {
    let shape = Circle()
    return shape
}
func getShapeTypeOther() -> some Shape {
    let shape = Triangle()
    return shape
}

if let circle = getShapeType() as? Circle {
    print(circle.name)
} 

if let traingle = getShapeTypeOther() as? Triangle {
    print(traingle.name)
}

Why SwiftUI Relies on some View

SwiftUI views are incredibly complex generic structs. When you embed a Text and a Button inside a VStack, the actual underlying concrete type is not just a “View”.

VStack {
    Text("Hello")
    Button("Click Me", action: {})
}

The true return type of that block of code is actually: VStack<TupleView<(Text, Button<Text>)>>

Imagine if you had to write that out for a complex screen with dozens of nested views!

// ❌ The Nightmare Without Opaque Types
var body: VStack<TupleView<(Text, VStack<TupleView<(Image, Text)>>)>> { ... }

By using some View, you tell the compiler to figure out the massive, ugly generic type for you, while you just focus on the interface.


Where to Use Opaque Types

1. SwiftUI (Obviously)

Every time you declare a View struct, or extract a subview into a computed property.

private var headerView: some View {
    HStack {
        Image(systemName: "star")
        Text("Favorites")
    }
}

2. Complex Collections

When you are heavily transforming collections (mapping, filtering, compacting), the resulting type becomes a massive chained generic.

// ✅ Good: Hiding the nasty generic return type
func getActiveUserNames() -> some Collection {
    let users = [User(name: "Alice", isActive: true), User(name: "Bob", isActive: false)]
    
    // The actual type here is LazyMapSequence<LazyFilterSequence<[User]>, String>
    // We do NOT want to type that out!
    return users.lazy.filter { $0.isActive }.map { $0.name }
}

3. Encapsulating Framework Logic

When you are building an SDK or a module and you want to return an object without exposing its internal concrete class to the public API.

public protocol PaymentProcessor {
    func charge(amount: Double)
}

internal struct StripeProcessor: PaymentProcessor { ... }

// The caller gets a PaymentProcessor, but has no idea it is Stripe under the hood.
public func makeProcessor() -> some PaymentProcessor {
    return StripeProcessor()
}

The Violation: Where NOT to Use Opaque Types

Because some means “a single, specific underlying concrete type known at compile time,” you cannot trick the compiler by returning different concrete types from the same function.

❌ The Anti-Pattern: Branching Concrete Types

// This will NOT compile
func makeShape(isRound: Bool) -> some Shape {
    if isRound {
        return Circle() // Type 1
    } else {
        return Rectangle() // Type 2
    }
}

Why this fails: The compiler is trying to resolve some Shape into one specific type. But it sees two possible types (Circle and Rectangle). It throws the error: “Function declares an opaque return type, but the return statements in its body do not have matching underlying types.”

✅ The Fix: Using any (Existential Types)

If you genuinely need to return entirely different concrete types dynamically based on runtime logic, you must use any instead of some.

// ✅ Using `any` allows different types to be returned
func makeShape(isRound: Bool) -> any Shape {
    if isRound {
        return Circle() 
    } else {
        return Rectangle() 
    }
}

Note: any incurs a slight performance penalty due to dynamic dispatch, so only use it when necessary.

✅ The SwiftUI Fix: Using @ViewBuilder

If you encounter this branching error in SwiftUI, the fix is not any View. The fix is using the @ViewBuilder attribute, which wraps your conditional logic into a single concrete type (_ConditionalContent).

// ✅ The SwiftUI way to handle branching
@ViewBuilder
func makeView(isLoggedIn: Bool) -> some View {
    if isLoggedIn {
        Text("Welcome Back!")
    } else {
        Button("Log In", action: {})
    }
}

some vs any : The Golden Rule

Swift 5.7 clarified the difference between these two keywords beautifully:

  • Use some (Opaque Type) when the underlying type is fixed and known at compile time, but you want to hide it from the caller. (Default to this for performance).
  • Use any (Existential Type) when the underlying type can change at runtime (e.g., in an array of mixed shapes, or a function that returns different structs based on an if/else).

Final Thoughts

Opaque types (some) are the secret sauce that makes SwiftUI’s declarative syntax possible. By using them in your own code, you can build highly optimized, strongly-typed APIs that abstract away ugly implementation details without sacrificing compile-time safety or runtime performance.