Protected access level / multiple class/struct/protocol APIs


(Andrey Tarantsov) #1

Now that the scoped access discussion is close(r) to being settled, let's tackle the next controversial topic: protected.

Expose

Objective-C allows multiple “APIs” to be exposed for a given class by virtue of writing additional header files. There are two common uses of that:

Use case 1: API for private clients

Foo.h
Foo-Private.h
Foo.m

Use case 2: API for subclasses, intended to be overridden

Foo.h
Foo-Subclasses.h // defines a method to override, e.g. layoutSubviews
Foo.m

Use case 3: API for subclasses, intended to be called

Foo.h
Foo-Subclasses.h // defines a dangerous helper method
Foo.m

Swift covers use case #1 with the internal (or perhaps soon-to-be moduleprivate) access modifier.

The problem

Swift core teams has prompted us to explore using internal and file-private access levels to cover use cases #2 and #3, and I've faithfully tried to do just that. It works for many cases inside the code of apps, but it doesn't work if you're writing a library package and want to expose the “special extenders club” API to the clients of the library.

Here's the use case in more detail:

1. You have a library that exposes a class called Foo.
2. There are two different ways to use that library: you can just use Foo, or you can extend the library, perhaps subclassing Foo.
3. Those who opt for the second way to use the library (i.e. extending it) need access to a broader set of APIs.

Why do extenders need access to a broader set of APIs?

Basically, for safety and documentation purposes.

Considering use case #2:

The library often exposes very similar APIs, some of which are intended to be called (layoutIfNeeded), others intended to be extended (layoutSubviews). These typically sound very similar, and are easy to mix up, so you definitely want to document this difference, and ideally you also want to statically prevent the first (non-extending) type of clients from calling them.

Note that accidentally calling layoutSubviews instead of layoutIfNeeded is one of the worst kinds of mistakes; it might seem to work, and it might not be found until production, and the bug it causes may be weird and difficult to reproduce.

Considering use case #3:

A library may expose methods that should not be called by normal clients, but are useful when extending it:

3.1) They may carry substantially different guarantees from normal methods (e.g. require to be called only on a certain dispatch queue),

3.2) or may only be callable while another overridable method is executing,

3.3) or may simply allow mutating the internal state of the class (e.g. the state of UIGestureRecognizer) that is not supposed to be mutated from outside.

Swift currently requires making all of this public. :frowning:

The problem has tons of prior art.

C family of languages use multiple header files.

Ada has special access rules for subpackages (nicely explained in http://blog.spacesocket.com/2012/07/31/ada-child-packages/).

OOP languages have protected access levels.

What does extending mean in Swift?

I'm not sure about this. It certainly needs to include subclassing a class, but we may find other useful meanings.

In particular, writing an extension for a struct/class/protocol may raise similar concerns and may need similar treatment.

What do I propose?

I'm mainly looking for ideas and discussion, but as a strawman, let me put this out:

Introduce protected access modifier that allows the member to be accessed by:

1. Subclasses of a class.
2. Extenders of a struct or protocol.
3. Implementors of a protocol.

package FooKit:

public class Foo {
  public func amSafe() { ... }
  protected func amDangerous() { ... }
}

public struct Boo {
  public func amSafe() { ... }
  protected func amDangerous() { ... }
}

public protocol Moo {
  func amSafe()
}

public extension Moo {
  protected func amDangerous() { ... }
}

App:

  public class Bar: Foo {
    public func bar() {
      amDangerous()
    }
  }

  public extension Foo {
    public func boz() {
      amDangerous()
    }
  }

  public extension Boo {
    public func boz() {
      amDangerous()
    }
  }

  public class Boz: Moo {
    public func fubar() {
      amDangerous()
    }
  }

So what do you think?

A.


(Tino) #2

A more sophisticated rights system isn't necessary, but actually, we could build everything with nothing but "public", so it is always a compromise between simplicity and expressiveness…
Imho it would be nice to be able to mark a method that is only there to be overridden and should never be called directly, but I don't think the compiler has to enforce this:
An official way to document the intent that affects autocompletion would be sufficient for me.

Besides the question where (class/module/public…) something is visible, there is also the question of what can or has to be done with a method:

- callable (read for properties)
- can override, call to super enforced
- can override
- has to be overridden (abstract)
- properties only: Write access

If there is an elegant way to handle all use cases, I'd strongly support this direction.


(Andrey Tarantsov) #3

Imho it would be nice to be able to mark a method that is only there to be overridden and should never be called directly, but I don't think the compiler has to enforce this:
An official way to document the intent that affects autocompletion would be sufficient for me.

An interesting idea that I see reflected in another proposal (intendedusage doc tag, or something like that).

Why, though? If we can express it, why not also make it a part of the signature and get warnings/errors on violations?

I have an argument in favor of annotations:

+ The documentation is known to lie and to get out of date, even when acting on best intentions. I know mine did, and I'm writing a lot less of it now. So I also see compiler-enforced annotations as “more reliable documentation”.

What are other possible arguments for and against?

- callable (read for properties)
- can override, call to super enforced
- can override
- has to be overridden (abstract)
- properties only: Write access

You're right, perhaps this isn't so much about access as it is about intended usage. (Not sure what that distinction means in practice, though.)

A.


(Howard Lovatt) #4

I tend to think an intended use annotation that could also be used in Obj-C
would be better than protected. The problem with protected is that it
provides virtually no protection at all; you can trivially expose it in a
derived class, e.g.:

class Protected {

protected func onlyDerived() { ... }

}

class Derived: Protected {

public override func onlyDerived() { super.onlyDerived() }

}

let d = Derived()
d.onlyDerived() // Protection lost

Therefore an annotation would be just as effective.

  -- Howard.

···

On 29 March 2016 at 21:54, Andrey Tarantsov via swift-evolution < swift-evolution@swift.org> wrote:

> Imho it would be nice to be able to mark a method that is only there to
be overridden and should never be called directly, but I don't think the
compiler has to enforce this:
> An official way to document the intent that affects autocompletion would
be sufficient for me.

An interesting idea that I see reflected in another proposal
(intendedusage doc tag, or something like that).

Why, though? If we can express it, why not also make it a part of the
signature and get warnings/errors on violations?

I have an argument in favor of annotations:

+ The documentation is known to lie and to get out of date, even when
acting on best intentions. I know mine did, and I'm writing a lot less of
it now. So I also see compiler-enforced annotations as “more reliable
documentation”.

What are other possible arguments for and against?

> - callable (read for properties)
> - can override, call to super enforced
> - can override
> - has to be overridden (abstract)
> - properties only: Write access

You're right, perhaps this isn't so much about access as it is about
intended usage. (Not sure what that distinction means in practice, though.)

A.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Dietmar Planitzer) #5

Well that would be true if we assume that protected would work that way. Considering that this:

private class A { … }

public class B : A { … }

is not allowed in Swift, I don’t see a good reason why an override of a protected method should be allowed to upgrade the access level to public. On the other hand this:

public class A {
    private func foo() {
        print("A")
    }
}

public class B : A {
    public override func foo() {
        print(“B”)
    }
}

happens to actually work if both A and B are defined in the same file - which is rather unexpected. I would have expected that Swift would in general not allow overrides to upgrade the inherited access level. Eg exposing semantics which is embodied in a private or protected method should require a conscisous design decision and should require the designer to introduce a separate method name which is part of the public API. The public method can then call through to the private or protected method as needed.

Anyway, I do think that a protected access level would be a useful tool to have exactly because it would allow me to clearly communicated the intent of whether a method is only there for use by subclassers. Today I can’t do that and thus I have to mark the method as public and thus make it part of the overall class API which means that I end up making a much bigger promise than I actually intended to give.

Regards,

Dietmar Planitzer

···

On Mar 29, 2016, at 16:56, Howard Lovatt via swift-evolution <swift-evolution@swift.org> wrote:

I tend to think an intended use annotation that could also be used in Obj-C would be better than protected. The problem with protected is that it provides virtually no protection at all; you can trivially expose it in a derived class, e.g.:

class Protected {
protected func onlyDerived() { ... }
}

class Derived: Protected {
public override func onlyDerived() { super.onlyDerived() }
}

let d = Derived()
d.onlyDerived() // Protection lost

Therefore an annotation would be just as effective.

  -- Howard.

On 29 March 2016 at 21:54, Andrey Tarantsov via swift-evolution <swift-evolution@swift.org> wrote:

> Imho it would be nice to be able to mark a method that is only there to be overridden and should never be called directly, but I don't think the compiler has to enforce this:
> An official way to document the intent that affects autocompletion would be sufficient for me.

An interesting idea that I see reflected in another proposal (intendedusage doc tag, or something like that).

Why, though? If we can express it, why not also make it a part of the signature and get warnings/errors on violations?

I have an argument in favor of annotations:

+ The documentation is known to lie and to get out of date, even when acting on best intentions. I know mine did, and I'm writing a lot less of it now. So I also see compiler-enforced annotations as “more reliable documentation”.

What are other possible arguments for and against?

> - callable (read for properties)
> - can override, call to super enforced
> - can override
> - has to be overridden (abstract)
> - properties only: Write access

You're right, perhaps this isn't so much about access as it is about intended usage. (Not sure what that distinction means in practice, though.)

A.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Joe Groff) #6

That would still be a toothless restriction, since a subclass could define a new method:

public class B : A {
   public func foo_() {
       super.foo()
   }
}

From an interface perspective, there's no difference between a subclass defining a new method or overriding a method that happens to be private to the base class.

-Joe

···

On Mar 29, 2016, at 6:04 PM, Dietmar Planitzer via swift-evolution <swift-evolution@swift.org> wrote:

Well that would be true if we assume that protected would work that way. Considering that this:

private class A { … }

public class B : A { … }

is not allowed in Swift, I don’t see a good reason why an override of a protected method should be allowed to upgrade the access level to public. On the other hand this:

public class A {
   private func foo() {
       print("A")
   }
}

public class B : A {
   public override func foo() {
       print(“B”)
   }
}

happens to actually work if both A and B are defined in the same file - which is rather unexpected. I would have expected that Swift would in general not allow overrides to upgrade the inherited access level. Eg exposing semantics which is embodied in a private or protected method should require a conscisous design decision and should require the designer to introduce a separate method name which is part of the public API. The public method can then call through to the private or protected method as needed.


(Joe Groff) #7

Extensions further dilute the enforceability of "protected", since anyone would be able to use an extension to dump methods into a class's namespace and access its supposedly-protected bits.

-Joe

···

On Mar 29, 2016, at 6:18 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On Mar 29, 2016, at 6:04 PM, Dietmar Planitzer via swift-evolution <swift-evolution@swift.org> wrote:

Well that would be true if we assume that protected would work that way. Considering that this:

private class A { … }

public class B : A { … }

is not allowed in Swift, I don’t see a good reason why an override of a protected method should be allowed to upgrade the access level to public. On the other hand this:

public class A {
  private func foo() {
      print("A")
  }
}

public class B : A {
  public override func foo() {
      print(“B”)
  }
}

happens to actually work if both A and B are defined in the same file - which is rather unexpected. I would have expected that Swift would in general not allow overrides to upgrade the inherited access level. Eg exposing semantics which is embodied in a private or protected method should require a conscisous design decision and should require the designer to introduce a separate method name which is part of the public API. The public method can then call through to the private or protected method as needed.

That would still be a toothless restriction, since a subclass could define a new method:

public class B : A {
  public func foo_() {
      super.foo()
  }
}

From an interface perspective, there's no difference between a subclass defining a new method or overriding a method that happens to be private to the base class.


(Thorsten Seitz) #8

I have no problems with "protected" members being made accessible in subclasses or extensions because this requires conscious decisions and actions to extend the API.
Access levels are not there to protect against hacking, they are there to ensure that something can only be used through its given API. They are not there to restrict extending that API.

Having said that I'd love to have "protected".

-Thorsten

···

Am 30.03.2016 um 03:28 schrieb Joe Groff via swift-evolution <swift-evolution@swift.org>:

On Mar 29, 2016, at 6:18 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On Mar 29, 2016, at 6:04 PM, Dietmar Planitzer via swift-evolution <swift-evolution@swift.org> wrote:

Well that would be true if we assume that protected would work that way. Considering that this:

private class A { … }

public class B : A { … }

is not allowed in Swift, I don’t see a good reason why an override of a protected method should be allowed to upgrade the access level to public. On the other hand this:

public class A {
private func foo() {
     print("A")
}
}

public class B : A {
public override func foo() {
     print(“B”)
}
}

happens to actually work if both A and B are defined in the same file - which is rather unexpected. I would have expected that Swift would in general not allow overrides to upgrade the inherited access level. Eg exposing semantics which is embodied in a private or protected method should require a conscisous design decision and should require the designer to introduce a separate method name which is part of the public API. The public method can then call through to the private or protected method as needed.

That would still be a toothless restriction, since a subclass could define a new method:

public class B : A {
public func foo_() {
     super.foo()
}
}

From an interface perspective, there's no difference between a subclass defining a new method or overriding a method that happens to be private to the base class.

Extensions further dilute the enforceability of "protected", since anyone would be able to use an extension to dump methods into a class's namespace and access its supposedly-protected bits.

-Joe
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Dietmar Planitzer) #9

Well that would be true if we assume that protected would work that way. Considering that this:

private class A { … }

public class B : A { … }

is not allowed in Swift, I don’t see a good reason why an override of a protected method should be allowed to upgrade the access level to public. On the other hand this:

public class A {
  private func foo() {
      print("A")
  }
}

public class B : A {
  public override func foo() {
      print(“B”)
  }
}

happens to actually work if both A and B are defined in the same file - which is rather unexpected. I would have expected that Swift would in general not allow overrides to upgrade the inherited access level. Eg exposing semantics which is embodied in a private or protected method should require a conscisous design decision and should require the designer to introduce a separate method name which is part of the public API. The public method can then call through to the private or protected method as needed.

That would still be a toothless restriction, since a subclass could define a new method:

public class B : A {
  public func foo_() {
      super.foo()
  }
}

From an interface perspective, there's no difference between a subclass defining a new method or overriding a method that happens to be private to the base class.

If a method was marked private in the base class, then it is very likely that the name of the method, the design of its argument list and its return value did not go through the same detailed design review as if the method would have been meant to be part of the class’ interface from the start. So it’s rather unlikely that increasing the visibility in an override is good idea and in the spirit of the original writer of the private method. If on the other side I’m required to introduce a new method name if I want to make the semantics of the private method part of my public interface, then (a) it gives me an opportunity to think about what kind of interface I want to commit to and (b) it gives me stability because it doesn’t matter anymore what is going to happen over time to the name, argument list and return value of the private method since the name and signature of my public method is now decoupled from the ones of the private method.

Also, your argument would just as well apply to the scenario below since a class is a collection of methods when you look at it purely from an interface perspective:

private class A { … }

public class B : A { … }

but Swift doesn’t allow this.

Regards,

Dietmar Planitzer

···

On Mar 29, 2016, at 18:18, Joe Groff <jgroff@apple.com> wrote:

On Mar 29, 2016, at 6:04 PM, Dietmar Planitzer via swift-evolution <swift-evolution@swift.org> wrote:

-Joe


(Dietmar Planitzer) #10

Well that would be true if we assume that protected would work that way. Considering that this:

private class A { … }

public class B : A { … }

is not allowed in Swift, I don’t see a good reason why an override of a protected method should be allowed to upgrade the access level to public. On the other hand this:

public class A {
private func foo() {
     print("A")
}
}

public class B : A {
public override func foo() {
     print(“B”)
}
}

happens to actually work if both A and B are defined in the same file - which is rather unexpected. I would have expected that Swift would in general not allow overrides to upgrade the inherited access level. Eg exposing semantics which is embodied in a private or protected method should require a conscisous design decision and should require the designer to introduce a separate method name which is part of the public API. The public method can then call through to the private or protected method as needed.

That would still be a toothless restriction, since a subclass could define a new method:

public class B : A {
public func foo_() {
     super.foo()
}
}

From an interface perspective, there's no difference between a subclass defining a new method or overriding a method that happens to be private to the base class.

Extensions further dilute the enforceability of "protected", since anyone would be able to use an extension to dump methods into a class's namespace and access its supposedly-protected bits.

That is true assuming that Swift would allow an extension to call a protected method. But I don’t see a strict requirement that Swift would have to allow this.

There are two variants of extensions that are useful:

a) extension is used by the producer of a type to break up a large implementation into smaller pieces: in this case the implementor writes those extensions and in this case you can argue that an extension should allow the code writer to do everything that he can do in a base type. Eg allow the definition of stored properties and allow the calling of protected methods (and likely private methods too). Swift doesn’t currently really support this. Ideally it would be possible to put each extension into a separate file and things like accessing private members would still work.

b) extension is used by a consumer of a type to extend the type after the fact: in this case it makes sense that an extension comes with certain restrictions. Eg you can not add stored properties (which especially in the case of value types could change the nature of the value type drastically) and you can not call private or protected methods. This is what Swift’s extensions are today.

Regards,

Dietmar Planitzer

···

On Mar 29, 2016, at 18:28, Joe Groff <jgroff@apple.com> wrote:

On Mar 29, 2016, at 6:18 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On Mar 29, 2016, at 6:04 PM, Dietmar Planitzer via swift-evolution <swift-evolution@swift.org> wrote:

-Joe


(Diego Sánchez) #11

That's the key point.

"Protected" would increase the expressivity of the language by declaring
intent and exposing cleaner public APIs.

I don't think this idea should be dropped just because conscious
hacks/decisions
can workaround it, so big +1 for it.

···

2016-03-30 5:51 GMT+01:00 Thorsten Seitz via swift-evolution < swift-evolution@swift.org>:

I have no problems with "protected" members being made accessible in
subclasses or extensions because this requires conscious decisions and
actions to extend the API.
Access levels are not there to protect against hacking, they are there to
ensure that something can only be used through its given API. They are not
there to restrict extending that API.

Having said that I'd love to have "protected".

-Thorsten

> Am 30.03.2016 um 03:28 schrieb Joe Groff via swift-evolution < > swift-evolution@swift.org>:
>
>
>>> On Mar 29, 2016, at 6:18 PM, Joe Groff via swift-evolution < > swift-evolution@swift.org> wrote:
>>>
>>>
>>> On Mar 29, 2016, at 6:04 PM, Dietmar Planitzer via swift-evolution < > swift-evolution@swift.org> wrote:
>>>
>>> Well that would be true if we assume that protected would work that
way. Considering that this:
>>>
>>> private class A { … }
>>>
>>> public class B : A { … }
>>>
>>> is not allowed in Swift, I don’t see a good reason why an override of
a protected method should be allowed to upgrade the access level to public.
On the other hand this:
>>>
>>> public class A {
>>> private func foo() {
>>> print("A")
>>> }
>>> }
>>>
>>> public class B : A {
>>> public override func foo() {
>>> print(“B”)
>>> }
>>> }
>>>
>>> happens to actually work if both A and B are defined in the same file
- which is rather unexpected. I would have expected that Swift would in
general not allow overrides to upgrade the inherited access level. Eg
exposing semantics which is embodied in a private or protected method
should require a conscisous design decision and should require the designer
to introduce a separate method name which is part of the public API. The
public method can then call through to the private or protected method as
needed.
>>
>> That would still be a toothless restriction, since a subclass could
define a new method:
>>
>> public class B : A {
>> public func foo_() {
>> super.foo()
>> }
>> }
>>
>> From an interface perspective, there's no difference between a subclass
defining a new method or overriding a method that happens to be private to
the base class.
>
> Extensions further dilute the enforceability of "protected", since
anyone would be able to use an extension to dump methods into a class's
namespace and access its supposedly-protected bits.
>
> -Joe
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Andrey Tarantsov) #12

The problem with protected is that it provides virtually no protection at all; you can trivially expose it in a derived class

···

+

Extensions further dilute the enforceability of "protected", since anyone would be able to use an extension to dump methods into a class's namespace and access its supposedly-protected bits.

I don't understand the need to protect against exposing something deliberately. We don't have a goal of restricting other developers, we're only saving them from accidental mistakes.

If a method was marked private in the base class, then it is very likely that the name of the method, the design of its argument list and its return value did not go through the same detailed design review as if the method would have been meant to be part of the class’ interface from the start. So it’s rather unlikely that increasing the visibility in an override is good idea and in the spirit of the original writer of the private method.

The design review and whether something is a good idea is left as a responsibility for those subclasses that choose to expose methods. The intentions of the original class author don't override the intentions of the subclass author.

That said, I don't necessarily believe that the protected modifier is the best solution for the problems we're discussing. Some methods are not intended to be called at all, and protected doesn't solve that.

A.