Struct or Protocol? Navigating Tough Architectural Decisions in Large Swift Projects
As Swift developers, we constantly face architectural choices that can feel pivotal, especially in large-scale projects. One of the most…
Struct or Protocol? Navigating Tough Architectural Decisions in Large Swift Projects
As Swift developers, we constantly face architectural choices that can feel pivotal, especially in large-scale projects. One of the most common dilemmas is deciding when to use a struct versus a protocol, particularly in projects spread across multiple modules or packages.
In this article, I’ll walk you through a real-world scenario where this choice can be difficult, and I’ll discuss some key considerations — especially around performance and compilation times — that can guide you to the right decision.
The Scenario: AccountProtocol Dilemma in a Large Swift Project
Imagine you’re working on a large project built with multiple Swift Package Manager (SPM) modules. One of these modules defines an AccountProtocol, which represents an "account" entity in the system. The protocol looks something like this:
protocol AccountProtocol {
var id: String { get }
var score: Double { get }
var isActive: Bool { get }
}
Currently, this protocol has only one concrete implementation, and there are no immediate plans for different types of accounts. As an architect, you’re left wondering: Should this really be a protocol, or should we switch to a simple struct instead?
This scenario brings up a broader question we often face in large projects: When is a protocol the right choice, and when does a struct make more sense?
1. Protocols: Flexibility and Extensibility
One of the biggest strengths of a protocol is its ability to define a flexible contract that various types can conform to. Protocols allow you to:
- Support multiple implementations: If there’s a chance that you might need different types of accounts (e.g.,
PremiumAccount,TrialAccount), the protocol makes it easier to extend the system in the future. - Decouple modules: Protocols promote loose coupling between modules, adhering to SOLID principles like Dependency Inversion. This allows different parts of the codebase to interact with the
AccountProtocolwithout depending on specific implementations. - Testing flexibility: Protocols allow you to mock or stub different implementations for unit tests. This makes it easier to isolate dependencies and mock behaviors, improving the quality of your tests.
But here’s the catch: In this case, the protocol only has properties with fixed types, and no behavior. This means that we’re not abstracting any logic — just defining a “data shape.”
If you’re not expecting multiple implementations anytime soon, and there’s no behavior to abstract, the flexibility of a protocol might be more than you need. Let’s explore why.
2. Structs: Simplicity, Performance, and Compilation Time
Structs are value types in Swift, known for their simplicity and high performance due to Swift’s copy-on-write (COW) behavior. A struct would allow you to represent the same data as AccountProtocol without the overhead of dynamic dispatch or protocol witness tables.
Here’s why a struct might be the better fit in this case:
A. Performance Considerations
When you use protocols, Swift relies on dynamic dispatch to look up which method or property to call at runtime. This is done through a protocol witness table, which introduces some runtime overhead. Although this overhead is small, it can add up in performance-critical paths, especially if the protocol is used extensively.
On the other hand, structs are statically dispatched. This means that the compiler knows exactly which method or property to call at compile-time, leading to more optimized and faster code.
Example: Let’s compare a struct to a protocol-conforming type. A simple struct for Account might look like this:
struct Account {
let id: String
let score: Double
let isActive: Bool
}
Since this is a struct, Swift can handle it with direct memory access, making it much more efficient for scenarios where the account data is frequently accessed or updated. There’s no runtime lookup, and the data is stored contiguously in memory.
This difference in dispatching mechanisms is most noticeable in tight loops or high-frequency operations, where the small overhead of protocol dispatch can compound.
B. Compilation Time Considerations
In large projects with multiple modules and dependencies, compilation time can become a significant concern. Protocols can increase compile-time complexity because:
- The compiler needs to generate extra code to support dynamic dispatch via the protocol witness table.
- Swift’s type system has to check for protocol conformance across the codebase, which can add to the build time, especially if the protocol is used widely in different modules.
Structs, by contrast, are simpler and typically compile faster because they don’t require the same level of abstraction and dynamic dispatch handling.
If your project is already facing long build times, choosing a struct over a protocol for something like AccountProtocol can lead to quicker builds and simpler compilation. This can be a significant advantage in large projects, where small optimizations at the architecture level can lead to more developer productivity.
3. Deciding Between Future-Proofing and Over-Engineering
At this point, you might be asking yourself: Is the flexibility of a protocol worth the overhead, or is a struct the simpler and more efficient solution?
Here’s where we need to balance future-proofing against over-engineering:
- If you anticipate needing multiple account implementations in the future (e.g.,
PremiumAccount,TrialAccount), then the protocol gives you the flexibility to easily add these types without modifying the core architecture. - But if there’s no realistic need for multiple account types, and you’re just dealing with a fixed set of properties, a
structis almost always the better option. It simplifies the code, improves performance, and reduces compile-time complexity.
Remember: It’s easier to refactor from a protocol to a struct later than the reverse. If you start with a struct and later find a need for abstraction, you can introduce a protocol. However, moving from a struct to a protocol in a large, established project can be more difficult and risk introducing breaking changes.
4. When a Struct is the Right Choice
In this particular case — where the protocol consists only of properties with fixed types — it’s hard to justify the protocol abstraction. A struct offers:
- Better performance due to static dispatch and efficient memory usage.
- Faster compilation times in large projects, which can have a big impact as the project scales.
- Simpler code without unnecessary abstraction, making the codebase easier to maintain and reason about.
In Swift, structs are a natural choice for representing data. If your AccountProtocol is just a collection of properties, a struct is the better fit.
5. When a Protocol Still Makes Sense
That said, there are cases where a protocol is still a good choice. If:
- You anticipate multiple implementations of
AccountProtocolwith different behaviors or storage mechanisms (e.g., API-based accounts vs local storage accounts). - You need to mock or test different account types in isolation.
Then a protocol would give you the flexibility you need.
Conclusion: Struct vs Protocol in Large Swift Projects
Architectural decisions in large Swift projects are often about finding the right balance between flexibility and simplicity. When deciding between a struct and a protocol, consider:
- Performance: Structs offer better performance with static dispatch and efficient memory use.
- Compilation Time: Protocols can increase build times, especially in large, multi-module projects.
- Future-proofing: Protocols provide flexibility for multiple implementations, but if you don’t foresee needing them, a
structis simpler and more efficient.
In our case, where the protocol only defines properties with specific types, a struct is the better choice. It keeps the codebase simple, fast, and easy to maintain—without introducing unnecessary abstraction.
Ultimately, the right choice depends on your project’s needs. If you’re building for scalability and anticipate future variations, a protocol might be worth it. But when it comes to data with no varying behavior, a struct is your go-to tool.
This article was written with the help of AI, based on an original idea to explore architectural decisions in Swift projects, particularly when choosing between structs and protocols.
메타데이터
- post_id
- ccbfe662a8a5
- slug
- struct-or-protocol-navigating-tough-architectural-decisions-in-large-swift-projects-ccbfe662a8a5
- url
- https://medium.com/@emadrazo/struct-or-protocol-navigating-tough-architectural-decisions-in-large-swift-projects-ccbfe662a8a5
- canonical_url
- https://medium.com/@emadrazo/struct-or-protocol-navigating-tough-architectural-decisions-in-large-swift-projects-ccbfe662a8a5
- author_url
- https://medium.com/@emadrazo
- status
- ok
- fetched_at
- 2026-07-16 18:39:53