I think it's more like code that needs to be implemented by clients to match a specific pattern with AccessControl.
Tangential to the main discussion points of this thread, but there is one application of private protocol members I've wished for in practice that the language doesn't currently have an excellent solution for: default implementations of protocol requirements.
Take a hypothetical example:
/// Conforming types keep a tally of some type of event.
protocol Tallying {
var totalTally: Int { get }
/// Records that the associated event has occurred by incrementing `totalTally`.
mutating func tally()
}
In this case, the implementation of tally() is trivial and uninteresting, so it'd be great to give it a default implementation:
extension Tallying {
mutating func tally() {
totalTally += 1 // â error: left side of mutating operator isn't mutable: 'totalTally' is a get-only property
}
}
Ideally, it'd be great to be able to express
protocol Tallying {
var totalTally: Int { get private(set) }
}
but this isn't currently allowed. At the moment, you have to:
- Declare
totalTally's setter as public, - Keep
totalTallyas-is, but give it a default implementation in terms of, e.g., a property like_totalTally(public, but underscored to discourage direct use), or - Turn
Tallyinginto a class with a private setter fortotalTallyand an implementation oftally(), from which other classes can inherit
(1) opens the door for accidental misuse of the property; (2) makes accidental misuse less likely, but makes conforming to the protocol convoluted (especially if the protocol has a long list of requirements); (3) is only applicable for classes (which don't already need to inherit from a different existing type).
(I completely agree with Swift's take that a protocol should define the public, semantic interface of a type â but to some extent, you can argue that "this property must be internally mutable since the semantics of the protocol require it to change" could be included in that approach.)
It's worth noting several standard library protocols already have non-public requirements.
Hashablehas_rawHashValueSequencehas_customContainsEquatableElement,_copyToContiguousArray, and_copyContentsCollectionhas_customIndexOfEquatableElement,_customLastIndexOfEquatableElement, and_failEarlyRangeCheck
Protocols outside of the standard library cannot do this, though, since they don't follow the leading underscore rule.
See also: Swift Regret: Open Protocols
Thatâs actually still looks like a great idea to me. And even outside of library designs it is useful in the day-to-day code. Not often, but once in a while I wish for something like this.
Cool example. Letâs pivotâif thatâs too difficult to implement directly, why not just wrap some language features into clear, descriptive names and make them âpublic,â so they can be added to or removed from types/etc as needed? For example, you could make a struct behave like a class if desired. It might trigger a warning or error, but it gives you flexibility in other areas.
Similarly, you could make a class behave like a struct, and apply the same idea to protocols as well.
A similar approach could work like the Adapter Patternâin this case, you wouldn't even need to read through the entire API. You could simply conform to a protocol, class, or other type, and everything would be directly available in your custom implementation. All you'd need to do is add your specific logic.
Changing your system to match an external one isnât always a perfect solution, as updates may break parts of your system. Instead, you could handle such changes gracefullyâby designing an implementation that surfaces compile-time errors, avoids fatal breaks, and remains safe within Swiftâs type system.
Iâm currently working on something that involves the Composite Pattern , which enforces polymorphism and requires recursive composition. This approach could eliminate the need for downcasting and make the entire implementation feel more Swift-like and idiomatic.
I'll end with this thought: we even have a star-rating-like approach to something as important as software architecture characteristics. Instead of constantly juggling between low-level hacks and high-level abstractions, wouldnât it be better if the language itself offered consistent, built-in support to guide us toward well-structured design? Imagine having the full spectrum of toolsâfrom foundational constructs to sophisticated design strategiesâbacked by the language, helping you stay safe, expressive, and productive without relying on workarounds or clever tricks. In the end, great design shouldn't feel like a battle against the toolsâit should feel like the tools are working with you.
It's just my understanding and could be wrong, but Swift protocols basically closer to functional typeclasses (or traits) rather than to Java OOP-ish interfaces. Difference is in implementation and very subtle for some, but basically typeclasses used for functions like
func calculate<N: AdditiveArithmetic>(num1: N, num2: N) -> N {
num1 + num2 // we know for sure that AdditiveArithmetic have +
}
when you only need to know what are constraints for AdditiveArithmetic protocol, so private things are useless.
Talking of interfaces it's more about inheritance with classic OOP like dog is an animal. And I can see how can this be fancy to define for each conformant types all the things you want, but Swift is just a bit of a different nature. (also personally think it's sometimes harmful)
Now having this in mind:
think this constant juggling is not due to language and could be avoided. Like I know it's just an example, but why even having IAccount as a protocol in the first place?
IAccount is useful because I believe soundness is a solid foundation for what a programming language should offer. You donât need to introduce âinfinity +1â levels of complexity into a basic design that naturally evolves year by year. However, if you donât commit to improving along a clear, intentional pathâand instead just add features randomlyâyou might eventually overlook the most straightforward and effective implementations, even the ones that are simple yet powerful.
Swift offers a rich set of features, flexible design approaches, and impressive cross-language compatibilityâthat alone sets it apart. One of the things that continues to stand out is how intuitive and consistent Swift code feels.
So why not focus on improving that clarity and consistency at a broader level, and leave the rest for another time?
I believe you can get this kind of extra checking from a macro. Just donât have the macro actually declare anything new, then emit a diagnostic if you see a pattern you donât like. Forcing it into the protocol design just seems like an awkward match.
I see, any/some other opinion? ![]()
As Iâve been diving into the Swift language, trying to get more familiar with its nuances and design philosophy, I started noticing something interesting. This particular approach Iâve been exploring could actually make a strong caseâespecially for value types, where traditional object retention mechanisms simply donât apply.
Itâs not just limited to simple data structures either. When you think about object composition or containment, even in the context of structs, this idea becomes even more valuable. And if something proves useful for value types, chances are it's just as valuable for functions that work with temporary references during execution. That means better control, clearer semantics, and fewer surprises at runtime.
In fact, in some cases, this might even render the Decorator pattern unnecessary. Rather than wrapping and unwrapping layers of behavior, Swiftâs own capabilitiesâif guided and expanded the right wayâmight allow for more natural composition without the added boilerplate. Itâs a refreshing thought: simplifying your design while staying true to Swiftâs strengths.
Even with all the powerful features Swift offers, it feels like the growing number of macros could eventually start interfering with the languageâs overall design philosophy. Thereâs a risk that, if we keep patching around limitations instead of improving the core language model, weâll eventually reach a point where thereâs no clear path forward just a tangle of special cases and exceptions.
Take SwiftUI as an example. Imagine you have a computed property with a get-set, and inside it, you try to mutate an @State collection. Because Swift treats self as immutable within view closures, that code wonât compile. To work around it, youâre forced to mark the setter as nonmutating. A kind of promise to Swift that you're not mutating anything important. With that in place, Swift now treats the whole thing as read-only on the surface, while silently allowing @Stateto mutate memory behind the scenes. ![]()
Itâs a clever workaround, sure. But it also feels like a jarring detour in an otherwise elegant language.