Swift
programming
variables
protocols
type declaration

In Swift, how can I declare a variable of a specific type that conforms to one or more protocols?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In Swift, the right declaration depends on what you actually want to preserve: a concrete type, a protocol contract, or an exact generic type constrained by protocols. Those are related ideas, but they are not interchangeable, and confusing them is the main reason this topic feels harder than it first appears.

Declare the Concrete Type When the Type Is Truly Known

If you already know the exact type you want to store, declare that type directly. Protocol conformance is then checked where the type is defined, not where the variable is stored.

swift
1protocol Named {
2    var name: String { get }
3}
4
5protocol Runnable {
6    func run()
7}
8
9struct Worker: Named, Runnable {
10    let name: String
11
12    func run() {
13        print("running \(name)")
14    }
15}
16
17let worker: Worker = Worker(name: "job-1")
18worker.run()

This is the strongest and simplest declaration because the compiler knows the exact type at all times.

Use an Existential When Any Conforming Type Is Acceptable

If the variable should hold any value that conforms to one or more protocols, use an existential type with any.

swift
1protocol Named {
2    var name: String { get }
3}
4
5protocol Runnable {
6    func run()
7}
8
9struct Service: Named, Runnable {
10    let name: String
11
12    func run() {
13        print("service \(name) started")
14    }
15}
16
17let item: any Named & Runnable = Service(name: "payments")
18print(item.name)
19item.run()

Here, the variable is not specifically a Service. It is some runtime value that satisfies both protocol requirements.

This is a useful design when the caller should not depend on the exact concrete type.

Use Generics When You Need Constraints Without Type Erasure

Sometimes you want abstraction, but you also want Swift to preserve the concrete type. That is where generics are better than an existential variable.

swift
1protocol Named {
2    var name: String { get }
3}
4
5protocol Runnable {
6    func run()
7}
8
9func execute<T: Named & Runnable>(_ value: T) {
10    print("executing \(value.name)")
11    value.run()
12}

This still requires the argument to conform to both protocols, but it does not erase the concrete type into any Named & Runnable. That difference matters when type relationships between parameters, return values, or associated types need to stay visible to the compiler.

Understand the Real Design Choice

The phrase "a specific type that conforms to protocols" often hides two different goals:

  • you know the exact type and want to store that exact type
  • you only care that the value matches a protocol contract

Those lead to different declarations:

  • 'let x: MyConcreteType when the exact implementation matters'
  • 'let x: any P & Q when any conforming type is acceptable'

Choosing between them is not just syntax. It is an API design decision about how much type information should remain available.

Protocols With Associated Types Need Extra Care

Not every protocol can be used as a simple existential variable. If a protocol has an associated type or Self requirements, you may need generics or type erasure instead.

swift
1protocol Cache {
2    associatedtype Value
3    func get(_ key: String) -> Value?
4}

A protocol like this cannot always be treated the way newcomers expect with a plain existential variable, because the associated type still has to mean something concrete somewhere.

That is why Swift developers often choose between three strategies:

  • concrete type storage
  • existential storage with any
  • generic constraints for type-preserving abstraction

Use a Practical Rule of Thumb

A good everyday rule is:

  • use a concrete type when implementation details matter
  • use any protocol composition when storage flexibility matters
  • use generics when you want abstraction without losing static type information

That rule solves most day-to-day confusion around protocol-based declarations.

Common Pitfalls

  • Using an existential when a generic constraint is really needed.
  • Forgetting any in modern Swift when declaring existential protocol variables.
  • Expecting protocol existentials to preserve concrete type relationships.
  • Assuming protocols with associated types behave like simple storage types.
  • Over-abstracting when a plain concrete type declaration would have been clearer.

Summary

  • Declare the concrete type directly when the exact type is known and important.
  • Use any P & Q when the variable may hold any value conforming to multiple protocols.
  • Use generics when you want protocol constraints without erasing the concrete type.
  • Existentials and generics solve different problems in Swift.
  • Protocols with associated types often require generics or type erasure rather than a simple existential variable.

Course illustration
Course illustration

All Rights Reserved.