Why can't Swift protocols have private properties, and could Swift support access control within protocols? (protocols different roles)

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:

  1. Declare totalTally's setter as public,
  2. Keep totalTally as-is, but give it a default implementation in terms of, e.g., a property like _totalTally (public, but underscored to discourage direct use), or
  3. Turn Tallying into a class with a private setter for totalTally and an implementation of tally(), 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.)

7 Likes

It's worth noting several standard library protocols already have non-public requirements.

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

3 Likes

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.

2 Likes

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.

@AlexanderM

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?

1 Like

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.

4 Likes

I see, any/some other opinion? :smiley: