access control


(Ilya Belenkiy) #1

I hope that access control can be revisited. It is the number one complaint about Swift that I hear from experienced developers. The current solution came as a complete surprise to every ObjC developer I've talked to. The natural expectation was that a strong access control system would be part of a strong type system. I already submitted a pull request with a proposal for this after a lengthy discussion here, but so far it was ignored. I hope that this can be revisited. Even if most people who responded earlier in this list decided that they were happy with the current state, it represents a tiny fraction of people using the language, and at least all the people I talked to strongly disagree but just aren’t on the list.

Right now access control is file based and not API based. This is much easier to implement but useless to express that certain elements of a class are implementation details that are not meant to be used anywhere else (someone can add more code to the same file and get access to the implementation details without modifying the class). It’s also impossible to hide APIs that are meant only for customization points of subclasses.

Some of these problems could be solved with a convention like putting _ in front of protected and __ in front of private methods, and Cocoa uses a similar approach today. But this “solution” is in the same category as using prefixes to determine types. If Swift aims to have a strong type system, and it’s described as one of advantages of Swift over ObjC, it should help enforce this at the language level.

.NET has a good solution that respects the OOP terminology and deals with accessibility in modules and at the API level:
https://msdn.microsoft.com/en-us/library/wxh6fsc7.aspx

I think that a similar approach would work much better in Swift.

···


Ilya Belenkiy


(Dave Abrahams) #2

I hope that access control can be revisited. It is the number one
complaint about Swift that I hear from experienced developers. The
current solution came as a complete surprise to every ObjC developer
I've talked to. The natural expectation was that a strong access
control system would be part of a strong type system. I already
submitted a pull request with a proposal for this after a lengthy
discussion here, but so far it was ignored. I hope that this can be
revisited. Even if most people who responded earlier in this list
decided that they were happy with the current state, it represents a
tiny fraction of people using the language, and at least all the
people I talked to strongly disagree but just aren’t on the list.

Right now access control is file based and not API based. This is much
easier to implement but useless to express that certain elements of a
class are implementation details that are not meant to be used
anywhere else (someone can add more code to the same file and get
access to the implementation details without modifying the
class).

I agree that our current access control has real weaknesses, but I don't
agree you've identified one above. Anyone who can "add more code to the
same file" can just as easily modify/extend the class. There's nothing
about extending a class that ought to raise a red flag in Swift, because
it's perfectly legit to do so and use only the class' public APIs.

I'm going to let others respond to the rest of this in more detail, but
I should also add that we didn't do file-level access control because it
was easier; we did it because we thought it was a better model for
Swift, where types are liberally open for extension.

It’s also impossible to hide APIs that are meant only for
customization points of subclasses.

Some of these problems could be solved with a convention like putting
_ in front of protected and __ in front of private methods, and Cocoa
uses a similar approach today. But this “solution” is in the same
category as using prefixes to determine types. If Swift aims to have a
strong type system, and it’s described as one of advantages of Swift
over ObjC, it should help enforce this at the language level.

.NET has a good solution that respects the OOP terminology and deals
with accessibility in modules and at the API level:
https://msdn.microsoft.com/en-us/library/wxh6fsc7.aspx

I think that a similar approach would work much better in Swift.


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

Cheers,

···

on Sat Jan 23 2016, Ilya Belenkiy <swift-evolution@swift.org> wrote:

--
-Dave


(Thorsten Seitz) #3

I hope that access control can be revisited. It is the number one complaint about Swift that I hear from experienced developers. The current solution came as a complete surprise to every ObjC developer I've talked to. The natural expectation was that a strong access control system would be part of a strong type system. I already submitted a pull request with a proposal for this after a lengthy discussion here, but so far it was ignored. I hope that this can be revisited. Even if most people who responded earlier in this list decided that they were happy with the current state, it represents a tiny fraction of people using the language, and at least all the people I talked to strongly disagree but just aren’t on the list.

Right now access control is file based and not API based. This is much easier to implement but useless to express that certain elements of a class are implementation details that are not meant to be used anywhere else (someone can add more code to the same file and get access to the implementation details without modifying the class).

I think the file based approach for `private` has advantages because it allows putting several things that have to know each other deeply into the same file (C++ would use friend access for that).

It’s also impossible to hide APIs that are meant only for customization points of subclasses.

Here I agree. What I am really missing is the ability to declare something „protected“ in a protocol (or class) which is not part of the public API but which I then can use in its default implementation but which has to be provided by implementors of the protocol.

Example (shortened and simplified to just show the relevant parts):

public protocol StandardGraphTraversal {
  // push() and pop() are implementation details and not part of the API
  protected func push(vertex: Vertex)
  protected func pop() -> Vertex?

  public func next() -> Vertex?
}

public extension StandardGraphTraversal {

  // leaving out things like visitor callbacks and coloring the visited vertices
  public func next() -> Vertex? {
    guard let source = pop() else { // using pop() here
      return nil
    }
    for target in graph.neighborsOf(source) {
      if shouldFollowVertex(target) {
        push(vertex) // using push() here
      }
    }
  }
}

// supplying the implementation detail: using a queue results in breadth first traversal
public struct BreadthFirstTraversal : StandardGraphTraversal {
  var queue: Queue<Vertex> = Queue()

  protected func push(vertex: Vertex) { return queue.push(vertex) }
  protected func pop() -> Vertex? { return queue.pop() }
}

// supplying the implementation detail: using a stack results in depth first traversal
public struct DepthFirstTraversal : StandardGraphTraversal {
  var stack: Stack<Vertex> = Stack()

  protected func push(vertex: Vertex) { return stack.push(vertex) }
  protected func pop() -> Vertex? { return stack.pop() }
}
  
Currently I cannot express that in Swift and have to make push() and pop() public :frowning:

(Allowing `internal` in public protocols would be sufficient in my example but would not help in more general cases where the implementors would have to be provided in another module by the framework user).

.NET has a good solution that respects the OOP terminology and deals with accessibility in modules and at the API level:
https://msdn.microsoft.com/en-us/library/wxh6fsc7.aspx

`public` and `internal` seem to be quite the same as in Swift.
`private` is based on the type instead of the file. I think Swift’s file based `private` is better because it allows to express the same by putting a type into its file alone and allows to create strongly interdependent types by putting them together into the same file.
`protected` is what I’m missing as well
`protected internal` seems to be the union of `protected` and `internal`. I think that is not needed and is probably there to allow testing for which Swift has a nicer solution with @testable imports.

In short the only thing which is missing IMO is `protected`.

-Thorsten

···

Am 23.01.2016 um 16:56 schrieb Ilya Belenkiy via swift-evolution <swift-evolution@swift.org>:


(Brent Royal-Gordon) #4

Right now access control is file based and not API based. This is much easier to implement but useless to express that certain elements of a class are implementation details that are not meant to be used anywhere else (someone can add more code to the same file and get access to the implementation details without modifying the class). It’s also impossible to hide APIs that are meant only for customization points of subclasses.

I am strongly opposed to `local`. But frankly, I'm so sick of discussing it at this point that I think we should just go ahead and propose it. Everyone can write their reviews, the core team can make a decision, and whatever they decide, we can move on from this topic to more interesting things.

···

--
Brent Royal-Gordon
Architechies


(Ilya Belenkiy) #5

Anyone who can "add more code to the same file" can just as easily modify/extend the class.

Yes, but with an API based access control, anyone modifying / extending the class can instantly see the author’s intent from the API declaration, and the compiler can prevent *accidental* misuse. Deliberate misuse cannot be stopped no matter what access control is in place (as long as the coder can modify the code in the module). But at least with an API based access control, a deliberate misuse is very easy to spot when debugging / reviewing the code. With a file based access control, the only way to tell if an API is really private or can be safely used anywhere in the same file is to read the comments / source code. And it’s much easier to miss in maintenance, debugging, or code reviews. The only way to make it obvious in the current model is to adopt a convention like _ in front of methods.

There's nothing about extending a class that ought to raise a red flag in Swift, because it's perfectly legit to do so and use only the class' public APIs.

Unless the programmer extends a class in the same file. Then he can call anything marked private, and it’s impossible to tell without reading the code whether it violates the class invariants. There is no way to express this in Swift today. My proposal for local / scoped access control would solve this.

we didn't do file-level access control because it was easier; we did it because we thought it was a better model for Swift, where types are liberally open for extension.

That’s what I assumed until I saw someone say it in this list when someone else raised a similar concern. Types in C++ and C# are also liberally open for extension, but they provide much better support for expressing the intent of an API and protect against accidental misuse.

···

On Jan 23, 2016, at 3:45 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

on Sat Jan 23 2016, Ilya Belenkiy <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I hope that access control can be revisited. It is the number one
complaint about Swift that I hear from experienced developers. The
current solution came as a complete surprise to every ObjC developer
I've talked to. The natural expectation was that a strong access
control system would be part of a strong type system. I already
submitted a pull request with a proposal for this after a lengthy
discussion here, but so far it was ignored. I hope that this can be
revisited. Even if most people who responded earlier in this list
decided that they were happy with the current state, it represents a
tiny fraction of people using the language, and at least all the
people I talked to strongly disagree but just aren’t on the list.

Right now access control is file based and not API based. This is much
easier to implement but useless to express that certain elements of a
class are implementation details that are not meant to be used
anywhere else (someone can add more code to the same file and get
access to the implementation details without modifying the
class).

I agree that our current access control has real weaknesses, but I don't
agree you've identified one above. Anyone who can "add more code to the
same file" can just as easily modify/extend the class. There's nothing
about extending a class that ought to raise a red flag in Swift, because
it's perfectly legit to do so and use only the class' public APIs.

I'm going to let others respond to the rest of this in more detail, but
I should also add that we didn't do file-level access control because it
was easier; we did it because we thought it was a better model for
Swift, where types are liberally open for extension.

It’s also impossible to hide APIs that are meant only for
customization points of subclasses.

Some of these problems could be solved with a convention like putting
_ in front of protected and __ in front of private methods, and Cocoa
uses a similar approach today. But this “solution” is in the same
category as using prefixes to determine types. If Swift aims to have a
strong type system, and it’s described as one of advantages of Swift
over ObjC, it should help enforce this at the language level.

.NET has a good solution that respects the OOP terminology and deals
with accessibility in modules and at the API level:
https://msdn.microsoft.com/en-us/library/wxh6fsc7.aspx

I think that a similar approach would work much better in Swift.


Ilya Belenkiy
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

Cheers,

--
-Dave

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


(Ilya Belenkiy) #6

I am not suggesting to replace the meaning of private. I think that “file internal” or something similar would be a much more precise name for what is now called private in Swift. My suggestion is to add a scope based access level that would signal the author’s intent and make it enforceable by the compiler.

···

On Jan 24, 2016, at 3:44 AM, Thorsten Seitz <tseitz42@icloud.com> wrote:

Am 23.01.2016 um 16:56 schrieb Ilya Belenkiy via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

I hope that access control can be revisited. It is the number one complaint about Swift that I hear from experienced developers. The current solution came as a complete surprise to every ObjC developer I've talked to. The natural expectation was that a strong access control system would be part of a strong type system. I already submitted a pull request with a proposal for this after a lengthy discussion here, but so far it was ignored. I hope that this can be revisited. Even if most people who responded earlier in this list decided that they were happy with the current state, it represents a tiny fraction of people using the language, and at least all the people I talked to strongly disagree but just aren’t on the list.

Right now access control is file based and not API based. This is much easier to implement but useless to express that certain elements of a class are implementation details that are not meant to be used anywhere else (someone can add more code to the same file and get access to the implementation details without modifying the class).

I think the file based approach for `private` has advantages because it allows putting several things that have to know each other deeply into the same file (C++ would use friend access for that).

It’s also impossible to hide APIs that are meant only for customization points of subclasses.

Here I agree. What I am really missing is the ability to declare something „protected“ in a protocol (or class) which is not part of the public API but which I then can use in its default implementation but which has to be provided by implementors of the protocol.

Example (shortened and simplified to just show the relevant parts):

public protocol StandardGraphTraversal {
  // push() and pop() are implementation details and not part of the API
  protected func push(vertex: Vertex)
  protected func pop() -> Vertex?

  public func next() -> Vertex?
}

public extension StandardGraphTraversal {

  // leaving out things like visitor callbacks and coloring the visited vertices
  public func next() -> Vertex? {
    guard let source = pop() else { // using pop() here
      return nil
    }
    for target in graph.neighborsOf(source) {
      if shouldFollowVertex(target) {
        push(vertex) // using push() here
      }
    }
  }
}

// supplying the implementation detail: using a queue results in breadth first traversal
public struct BreadthFirstTraversal : StandardGraphTraversal {
  var queue: Queue<Vertex> = Queue()

  protected func push(vertex: Vertex) { return queue.push(vertex) }
  protected func pop() -> Vertex? { return queue.pop() }
}

// supplying the implementation detail: using a stack results in depth first traversal
public struct DepthFirstTraversal : StandardGraphTraversal {
  var stack: Stack<Vertex> = Stack()

  protected func push(vertex: Vertex) { return stack.push(vertex) }
  protected func pop() -> Vertex? { return stack.pop() }
}
  
Currently I cannot express that in Swift and have to make push() and pop() public :frowning:

(Allowing `internal` in public protocols would be sufficient in my example but would not help in more general cases where the implementors would have to be provided in another module by the framework user).

.NET has a good solution that respects the OOP terminology and deals with accessibility in modules and at the API level:
https://msdn.microsoft.com/en-us/library/wxh6fsc7.aspx

`public` and `internal` seem to be quite the same as in Swift.
`private` is based on the type instead of the file. I think Swift’s file based `private` is better because it allows to express the same by putting a type into its file alone and allows to create strongly interdependent types by putting them together into the same file.
`protected` is what I’m missing as well
`protected internal` seems to be the union of `protected` and `internal`. I think that is not needed and is probably there to allow testing for which Swift has a nicer solution with @testable imports.

In short the only thing which is missing IMO is `protected`.

-Thorsten


(Taras Zakharko) #7

I disagree with Ilya on his stance on ‘private’. I do understand the argumentation, but the reasoning seems overly formal to me and the classical solution (private visible only within the class) also comes with a number of drawbacks that result in things like friends declarations in C++. It is nice to have relationships between entities explicit, but one can go overboard with the rigidity.

However, I also strongly agree with Thorsten that a subclass (but not public) visible modifier has a lot of uses. I would prefer to have four levels of access:

private (file scope)
internal (module scope)
protected (subclass scope)
public

Best,

Taras

···

On 24 Jan 2016, at 09:44, Thorsten Seitz via swift-evolution <swift-evolution@swift.org> wrote:

Am 23.01.2016 um 16:56 schrieb Ilya Belenkiy via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

I hope that access control can be revisited. It is the number one complaint about Swift that I hear from experienced developers. The current solution came as a complete surprise to every ObjC developer I've talked to. The natural expectation was that a strong access control system would be part of a strong type system. I already submitted a pull request with a proposal for this after a lengthy discussion here, but so far it was ignored. I hope that this can be revisited. Even if most people who responded earlier in this list decided that they were happy with the current state, it represents a tiny fraction of people using the language, and at least all the people I talked to strongly disagree but just aren’t on the list.

Right now access control is file based and not API based. This is much easier to implement but useless to express that certain elements of a class are implementation details that are not meant to be used anywhere else (someone can add more code to the same file and get access to the implementation details without modifying the class).

I think the file based approach for `private` has advantages because it allows putting several things that have to know each other deeply into the same file (C++ would use friend access for that).

It’s also impossible to hide APIs that are meant only for customization points of subclasses.

Here I agree. What I am really missing is the ability to declare something „protected“ in a protocol (or class) which is not part of the public API but which I then can use in its default implementation but which has to be provided by implementors of the protocol.

Example (shortened and simplified to just show the relevant parts):

public protocol StandardGraphTraversal {
  // push() and pop() are implementation details and not part of the API
  protected func push(vertex: Vertex)
  protected func pop() -> Vertex?

  public func next() -> Vertex?
}

public extension StandardGraphTraversal {

  // leaving out things like visitor callbacks and coloring the visited vertices
  public func next() -> Vertex? {
    guard let source = pop() else { // using pop() here
      return nil
    }
    for target in graph.neighborsOf(source) {
      if shouldFollowVertex(target) {
        push(vertex) // using push() here
      }
    }
  }
}

// supplying the implementation detail: using a queue results in breadth first traversal
public struct BreadthFirstTraversal : StandardGraphTraversal {
  var queue: Queue<Vertex> = Queue()

  protected func push(vertex: Vertex) { return queue.push(vertex) }
  protected func pop() -> Vertex? { return queue.pop() }
}

// supplying the implementation detail: using a stack results in depth first traversal
public struct DepthFirstTraversal : StandardGraphTraversal {
  var stack: Stack<Vertex> = Stack()

  protected func push(vertex: Vertex) { return stack.push(vertex) }
  protected func pop() -> Vertex? { return stack.pop() }
}
  
Currently I cannot express that in Swift and have to make push() and pop() public :frowning:

(Allowing `internal` in public protocols would be sufficient in my example but would not help in more general cases where the implementors would have to be provided in another module by the framework user).

.NET has a good solution that respects the OOP terminology and deals with accessibility in modules and at the API level:
https://msdn.microsoft.com/en-us/library/wxh6fsc7.aspx

`public` and `internal` seem to be quite the same as in Swift.
`private` is based on the type instead of the file. I think Swift’s file based `private` is better because it allows to express the same by putting a type into its file alone and allows to create strongly interdependent types by putting them together into the same file.
`protected` is what I’m missing as well
`protected internal` seems to be the union of `protected` and `internal`. I think that is not needed and is probably there to allow testing for which Swift has a nicer solution with @testable imports.

In short the only thing which is missing IMO is `protected`.

-Thorsten

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


(Ilya Belenkiy) #8

"Local" addresses an important problem in the current access control,
should be easy to implement and support, and doesn't break any existing
code. If you are not going to use it, that's ok, but plenty of people will.
It's the number one complaint that I hear about Swift from anybody I asked
about the language. So I understand "I won't use it", but I don't
understand "I am strongly against".

I also think that 2 rounds of discussion are more than enough to schedule a
review and asked about this. It looks like my question was ignored. I will
definitely keep trying one way or another until it goes through a formal
review. Even if it gets rejected, at least there will be an official
statement about why real data encapsulation is not important enough or
needed for Swift. And if it's accepted, a lot of people who are not on this
list will be very happy.

···

On Thu, Jan 28, 2016 at 5:56 AM Brent Royal-Gordon <brent@architechies.com> wrote:

> Right now access control is file based and not API based. This is much
easier to implement but useless to express that certain elements of a class
are implementation details that are not meant to be used anywhere else
(someone can add more code to the same file and get access to the
implementation details without modifying the class). It’s also impossible
to hide APIs that are meant only for customization points of subclasses.

I am strongly opposed to `local`. But frankly, I'm so sick of discussing
it at this point that I think we should just go ahead and propose it.
Everyone can write their reviews, the core team can make a decision, and
whatever they decide, we can move on from this topic to more interesting
things.

--
Brent Royal-Gordon
Architechies


(Joe Groff) #9

Anyone who can "add more code to the same file" can just as easily modify/extend the class.

Yes, but with an API based access control, anyone modifying / extending the class can instantly see the author’s intent from the API declaration, and the compiler can prevent *accidental* misuse. Deliberate misuse cannot be stopped no matter what access control is in place (as long as the coder can modify the code in the module). But at least with an API based access control, a deliberate misuse is very easy to spot when debugging / reviewing the code. With a file based access control, the only way to tell if an API is really private or can be safely used anywhere in the same file is to read the comments / source code. And it’s much easier to miss in maintenance, debugging, or code reviews. The only way to make it obvious in the current model is to adopt a convention like _ in front of methods.

There's nothing about extending a class that ought to raise a red flag in Swift, because it's perfectly legit to do so and use only the class' public APIs.

Unless the programmer extends a class in the same file. Then he can call anything marked private, and it’s impossible to tell without reading the code whether it violates the class invariants. There is no way to express this in Swift today. My proposal for local / scoped access control would solve this.

This doesn't change if you constrain access to a class's members to the primary class declaration. Anyone extending the class within the 'class Foo { }' braces also has to understand the class invariants.

-Joe

···

On Jan 23, 2016, at 2:14 PM, Ilya Belenkiy via swift-evolution <swift-evolution@swift.org> wrote:

we didn't do file-level access control because it was easier; we did it because we thought it was a better model for Swift, where types are liberally open for extension.

That’s what I assumed until I saw someone say it in this list when someone else raised a similar concern. Types in C++ and C# are also liberally open for extension, but they provide much better support for expressing the intent of an API and protect against accidental misuse.

On Jan 23, 2016, at 3:45 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

on Sat Jan 23 2016, Ilya Belenkiy <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I hope that access control can be revisited. It is the number one
complaint about Swift that I hear from experienced developers. The
current solution came as a complete surprise to every ObjC developer
I've talked to. The natural expectation was that a strong access
control system would be part of a strong type system. I already
submitted a pull request with a proposal for this after a lengthy
discussion here, but so far it was ignored. I hope that this can be
revisited. Even if most people who responded earlier in this list
decided that they were happy with the current state, it represents a
tiny fraction of people using the language, and at least all the
people I talked to strongly disagree but just aren’t on the list.

Right now access control is file based and not API based. This is much
easier to implement but useless to express that certain elements of a
class are implementation details that are not meant to be used
anywhere else (someone can add more code to the same file and get
access to the implementation details without modifying the
class).

I agree that our current access control has real weaknesses, but I don't
agree you've identified one above. Anyone who can "add more code to the
same file" can just as easily modify/extend the class. There's nothing
about extending a class that ought to raise a red flag in Swift, because
it's perfectly legit to do so and use only the class' public APIs.

I'm going to let others respond to the rest of this in more detail, but
I should also add that we didn't do file-level access control because it
was easier; we did it because we thought it was a better model for
Swift, where types are liberally open for extension.

It’s also impossible to hide APIs that are meant only for
customization points of subclasses.

Some of these problems could be solved with a convention like putting
_ in front of protected and __ in front of private methods, and Cocoa
uses a similar approach today. But this “solution” is in the same
category as using prefixes to determine types. If Swift aims to have a
strong type system, and it’s described as one of advantages of Swift
over ObjC, it should help enforce this at the language level.

.NET has a good solution that respects the OOP terminology and deals
with accessibility in modules and at the API level:
https://msdn.microsoft.com/en-us/library/wxh6fsc7.aspx

I think that a similar approach would work much better in Swift.


Ilya Belenkiy
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

Cheers,

--
-Dave

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto: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


(Dave Abrahams) #10

Anyone who can "add more code to the same file" can just as easily modify/extend the class.

Yes, but with an API based access control,

I don't know what that term means, sorry.

anyone modifying / extending the class can instantly see the author’s
intent from the API declaration,

I don't see how that's different from what we have in Swift. Private
APIs are the ones you don't want anyone to touch because they can break
your invariants.

and the compiler can prevent *accidental* misuse. Deliberate misuse
cannot be stopped no matter what access control is in place (as long
as the coder can modify the code in the module). But at least with an
API based access control, a deliberate misuse is very easy to spot
when debugging / reviewing the code. With a file based access control,
the only way to tell if an API is really private or can be safely used
anywhere in the same file is to read the comments / source code.

Seeing the keyword “private” isn't enough for you in Swift, but it is in
C++? Why?

And it’s much easier to miss in maintenance, debugging, or code
reviews. The only way to make it obvious in the current model is to
adopt a convention like _ in front of methods.

The only reason we're doing that in the standard library is that we're
currently forced to make some implementation details formally public,
and we can't use “private” because <reasons>. If it weren't for these
limitations, we'd be very happy with “private.” Defining one type per
file is usually a good practice anyway.

There's nothing about extending a class that ought to raise a red
flag in Swift, because it's perfectly legit to do so and use only
the class' public APIs.

Unless the programmer extends a class in the same file. Then he can
call anything marked private, and it’s impossible to tell without
reading the code whether it violates the class invariants.

Without prohibiting class extensions from being made in the same file
where the class is defined—a capability we want to keep available to
class authors—that will always be the case. Therefore, you have the
same scenario as in C++: anyone who modifies the file where a class is
defined might be violating its invariants. It's just that in C++ the
violator has to write code between a particular set of braces. When
reviewing diffs it's very common not to have enough context to see those
braces anyway.

There is no way to express this in Swift today. My proposal for local
/ scoped access control would solve this.

we didn't do file-level access control because it was easier; we
did it because we thought it was a better model for Swift, where
types are liberally open for extension.

That’s what I assumed until I saw someone say it in this list when
someone else raised a similar concern. Types in C++ and C# are also
liberally open for extension,

Not at all in the same way. In C++, you can't add methods to a class
outside the file where it's declared unless there's some really horrible
preprocessor stuff going on, and even then the class author would have
had to explicitly left that door open for you.

···

on Sat Jan 23 2016, Ilya Belenkiy <ilya.belenkiy-AT-gmail.com> wrote:

--
-Dave


(David Waite) #11

<snip>

With a file based access control, the only way to tell if an API is really private or can be safely used anywhere in the same file is to read the comments / source code. And it’s much easier to miss in maintenance, debugging, or code reviews. The only way to make it obvious in the current model is to adopt a convention like _ in front of methods.

Within the same file, I would expect a member of my team writing code has the domain knowledge to do so correctly.

Is the primary use case meant for files which have grown too large/complex for the authors to be expected to comprehend the type state invariance of the code they are currently working on?

Or is the thought that developers may add code to the file to get access to private api (and implicit action), while they would not be willing to edit the access levels to do so otherwise (an explicit action)? This sounds like a process issue within a team, as access control flags will not necessarily restrict the process of modifying the code in certain ways for people who are already tasked with such without such external process/ramifications in place.

There's nothing about extending a class that ought to raise a red flag in Swift, because it's perfectly legit to do so and use only the class' public APIs.

Unless the programmer extends a class in the same file. Then he can call anything marked private, and it’s impossible to tell without reading the code whether it violates the class invariants. There is no way to express this in Swift today. My proposal for local / scoped access control would solve this.

There is nothing about someone editing that file to change a “local" access modifier to “internal" which will raise a red flag with Swift. Is this something that you plan to detect and block via a SCM commit hook/code review product?

-DW

···

On Jan 23, 2016, at 3:14 PM, Ilya Belenkiy via swift-evolution <swift-evolution@swift.org> wrote:


(Ilya Belenkiy) #12

Yes, but with an API based access control,

I don't know what that term means, sorry.

I had to come up with this term to distinguish it from a file based access control. In API based access control, visibility of anything is tied to the declaration instead of the file it is in. It’s the model that C++ and C# ( and I am sure many others) use. In file based access control, visibility of anything is tied to the file it is in and has little to do with the class API itself. It’s the model that Swift currently follows.

anyone modifying / extending the class can instantly see the author’s
intent from the API declaration,

I don't see how that's different from what we have in Swift. Private
APIs are the ones you don't want anyone to touch because they can break
your invariants.

In Swift, any additional code in the same source file can use the APIs declared private and break your invariants. The semantic meaning of private is not “use only inside the class” but “use only inside this file”. It is currently impossible to express “use only inside the class” semantics.

Seeing the keyword “private” isn't enough for you in Swift, but it is in
C++? Why?

Because in Swift, private means “use only inside this file” and not “use only inside this class / scope”. In C++ private means “use only inside this class”. Moreover, C++ puts a special emphasis on this — variables are private inside the class unless the code specifically says otherwise.

Defining one type per file is usually a good practice anyway.

This really depends on the context. There is value in keeping similar concepts / implementations in the same file. If one class and one extension per file become a requirement, then private will have the same semantics as in C++. It will also become very inconvenient to write code because even a small extension with 1 method that can fit on one line will require a separate file.

Without prohibiting class extensions from being made in the same file
where the class is defined—a capability we want to keep available to
class authors—that will always be the case.

It doesn’t have to be. My proposal (still a pull request) provides a way to explicitly declare a function or a property to be private to the scope where it is defined. So extensions and subclasses declared in the same file cannot access it and break the invariant. If it’s applied to both class definitions and extensions, it’s very consistent and provides an explicit declaration of intent that can (and should) be enforced by the compiler.

Therefore, you have the
same scenario as in C++: anyone who modifies the file where a class is
defined might be violating its invariants. It's just that in C++ the
violator has to write code between a particular set of braces. When
reviewing diffs it's very common not to have enough context to see those
braces anyway.

This is a crucial distinction. That particular set of braces defines a scope which acts as a black box and hides the implementation details. Anything outside the scope cannot damage the internal state, and the compiler *enforces* it. In Swift, the compiler only enforces that the API is not visible from another file because the language has no way to express “this should be visible only in the scope to hide implementation details”. The only exception to that are functions inside other functions and local variables — right now this is the only way to completely hide implementation details, but it’s very limited.

My main 2 points are that it’s impossible to express the intent of a truly local access level to hide implementation details, and because of that, the compiler cannot enforce this intent.

When
reviewing diffs it's very common not to have enough context to see those
braces anyway.

This depends on how thoroughly the code is reviewed. But the main point there is that when you find out, you know that whoever worked around a private API did so knowing that he was introducing a hack. Because Swift has no way of expressing “this is private to the class, not just file it’s in”, someone may not know and make a mistake, and the compiler cannot catch it. My proposal makes the intent expressible in the language and enforceable by the compiler.

Not at all in the same way. In C++, you can't add methods to a class
outside the file where it's declared unless there's some really horrible
preprocessor stuff going on, and even then the class author would have
had to explicitly left that door open for you.

True, but my proposal deals with extensions the same way it does with class definitions — it provides a way to hide implementation details in a scope (whether it be a class definition or an extension scope) and make them invisible to everything else including extensions or other extensions or anything else in the same file. It provides a way to express the intent in the language and enforce it by the compiler.

···

On Jan 23, 2016, at 5:43 PM, Dave Abrahams <dabrahams@apple.com> wrote:

on Sat Jan 23 2016, Ilya Belenkiy <ilya.belenkiy-AT-gmail.com> wrote:

Anyone who can "add more code to the same file" can just as easily modify/extend the class.

Yes, but with an API based access control,

I don't know what that term means, sorry.

anyone modifying / extending the class can instantly see the author’s
intent from the API declaration,

I don't see how that's different from what we have in Swift. Private
APIs are the ones you don't want anyone to touch because they can break
your invariants.

and the compiler can prevent *accidental* misuse. Deliberate misuse
cannot be stopped no matter what access control is in place (as long
as the coder can modify the code in the module). But at least with an
API based access control, a deliberate misuse is very easy to spot
when debugging / reviewing the code. With a file based access control,
the only way to tell if an API is really private or can be safely used
anywhere in the same file is to read the comments / source code.

Seeing the keyword “private” isn't enough for you in Swift, but it is in
C++? Why?

And it’s much easier to miss in maintenance, debugging, or code
reviews. The only way to make it obvious in the current model is to
adopt a convention like _ in front of methods.

The only reason we're doing that in the standard library is that we're
currently forced to make some implementation details formally public,
and we can't use “private” because <reasons>. If it weren't for these
limitations, we'd be very happy with “private.” Defining one type per
file is usually a good practice anyway.

There's nothing about extending a class that ought to raise a red
flag in Swift, because it's perfectly legit to do so and use only
the class' public APIs.

Unless the programmer extends a class in the same file. Then he can
call anything marked private, and it’s impossible to tell without
reading the code whether it violates the class invariants.

Without prohibiting class extensions from being made in the same file
where the class is defined—a capability we want to keep available to
class authors—that will always be the case. Therefore, you have the
same scenario as in C++: anyone who modifies the file where a class is
defined might be violating its invariants. It's just that in C++ the
violator has to write code between a particular set of braces. When
reviewing diffs it's very common not to have enough context to see those
braces anyway.

There is no way to express this in Swift today. My proposal for local
/ scoped access control would solve this.

we didn't do file-level access control because it was easier; we
did it because we thought it was a better model for Swift, where
types are liberally open for extension.

That’s what I assumed until I saw someone say it in this list when
someone else raised a similar concern. Types in C++ and C# are also
liberally open for extension,

Not at all in the same way. In C++, you can't add methods to a class
outside the file where it's declared unless there's some really horrible
preprocessor stuff going on, and even then the class author would have
had to explicitly left that door open for you.

--
-Dave


(Ilya Belenkiy) #13

Yes, but that understanding doesn't need to be as deep. Also, with The API
based approach, the compiler can enforce the class author's intent, and the
intent is very clearly spelled out.

···

On Sat, Jan 23, 2016 at 5:20 PM Joe Groff <jgroff@apple.com> wrote:

On Jan 23, 2016, at 2:14 PM, Ilya Belenkiy via swift-evolution < > swift-evolution@swift.org> wrote:

Anyone who can "add more code to the same file" can just as easily
modify/extend the class.

Yes, but with an API based access control, anyone modifying / extending
the class can instantly see the author’s intent from the API declaration,
and the compiler can prevent *accidental* misuse. Deliberate misuse cannot
be stopped no matter what access control is in place (as long as the coder
can modify the code in the module). But at least with an API based access
control, a deliberate misuse is very easy to spot when debugging /
reviewing the code. With a file based access control, the only way to tell
if an API is really private or can be safely used anywhere in the same file
is to read the comments / source code. And it’s much easier to miss in
maintenance, debugging, or code reviews. The only way to make it obvious in
the current model is to adopt a convention like _ in front of methods.

There's nothing about extending a class that ought to raise a red flag in
Swift, because it's perfectly legit to do so and use only the class'
public APIs.

Unless the programmer extends a class in the same file. Then he can call
anything marked private, and it’s impossible to tell without reading the
code whether it violates the class invariants. There is no way to express
this in Swift today. My proposal for local / scoped access control would
solve this.

This doesn't change if you constrain access to a class's members to the
primary class declaration. Anyone extending the class within the 'class Foo
{ }' braces also has to understand the class invariants.

-Joe

we didn't do file-level access control because it was easier; we did it
because we thought it was a better model for Swift, where types are
liberally open for extension.

That’s what I assumed until I saw someone say it in this list when someone
else raised a similar concern. Types in C++ and C# are also liberally open
for extension, but they provide much better support for expressing the
intent of an API and protect against accidental misuse.

On Jan 23, 2016, at 3:45 PM, Dave Abrahams via swift-evolution < > swift-evolution@swift.org> wrote:

on Sat Jan 23 2016, Ilya Belenkiy <swift-evolution@swift.org> wrote:

I hope that access control can be revisited. It is the number one
complaint about Swift that I hear from experienced developers. The
current solution came as a complete surprise to every ObjC developer
I've talked to. The natural expectation was that a strong access
control system would be part of a strong type system. I already
submitted a pull request with a proposal for this after a lengthy
discussion here, but so far it was ignored. I hope that this can be
revisited. Even if most people who responded earlier in this list
decided that they were happy with the current state, it represents a
tiny fraction of people using the language, and at least all the
people I talked to strongly disagree but just aren’t on the list.

Right now access control is file based and not API based. This is much
easier to implement but useless to express that certain elements of a
class are implementation details that are not meant to be used
anywhere else (someone can add more code to the same file and get
access to the implementation details without modifying the
class).

I agree that our current access control has real weaknesses, but I don't
agree you've identified one above. Anyone who can "add more code to the
same file" can just as easily modify/extend the class. There's nothing
about extending a class that ought to raise a red flag in Swift, because
it's perfectly legit to do so and use only the class' public APIs.

I'm going to let others respond to the rest of this in more detail, but
I should also add that we didn't do file-level access control because it
was easier; we did it because we thought it was a better model for
Swift, where types are liberally open for extension.

It’s also impossible to hide APIs that are meant only for
customization points of subclasses.

Some of these problems could be solved with a convention like putting
_ in front of protected and __ in front of private methods, and Cocoa
uses a similar approach today. But this “solution” is in the same
category as using prefixes to determine types. If Swift aims to have a
strong type system, and it’s described as one of advantages of Swift
over ObjC, it should help enforce this at the language level.

.NET has a good solution that respects the OOP terminology and deals
with accessibility in modules and at the API level:
https://msdn.microsoft.com/en-us/library/wxh6fsc7.aspx

I think that a similar approach would work much better in Swift.


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

Cheers,

--
-Dave

_______________________________________________
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


(Ilya Belenkiy) #14

Formal means enforceable by the compiler. File based access control is
useful as well but not as a substitute for real data encapsulation.

···

On Mon, Jan 25, 2016 at 8:13 AM Taras Zakharko < taras.zakharko@googlemail.com> wrote:

I disagree with Ilya on his stance on ‘private’. I do understand the
argumentation, but the reasoning seems overly formal to me and the
classical solution (private visible only within the class) also comes with
a number of drawbacks that result in things like friends declarations in
C++. It is nice to have relationships between entities explicit, but one
can go overboard with the rigidity.

However, I also strongly agree with Thorsten that a subclass (but not
public) visible modifier has a lot of uses. I would prefer to have four
levels of access:

private (file scope)
internal (module scope)
protected (subclass scope)
public

Best,

Taras

On 24 Jan 2016, at 09:44, Thorsten Seitz via swift-evolution < > swift-evolution@swift.org> wrote:

Am 23.01.2016 um 16:56 schrieb Ilya Belenkiy via swift-evolution < > swift-evolution@swift.org>:

I hope that access control can be revisited. It is the number one
complaint about Swift that I hear from experienced developers. The current
solution came as a complete surprise to every ObjC developer I've talked
to. The natural expectation was that a strong access control system would
be part of a strong type system. I already submitted a pull request with a
proposal for this after a lengthy discussion here, but so far it was
ignored. I hope that this can be revisited. Even if most people who
responded earlier in this list decided that they were happy with the
current state, it represents a tiny fraction of people using the language,
and at least all the people I talked to strongly disagree but just aren’t
on the list.

Right now access control is file based and not API based. This is much
easier to implement but useless to express that certain elements of a class
are implementation details that are not meant to be used anywhere else
(someone can add more code to the same file and get access to the
implementation details without modifying the class).

I think the file based approach for `private` has advantages because it
allows putting several things that have to know each other deeply into the
same file (C++ would use friend access for that).

It’s also impossible to hide APIs that are meant only for customization
points of subclasses.

Here I agree. What I am really missing is the ability to declare something
„protected“ in a protocol (or class) which is not part of the public API
but which I then can use in its default implementation but which has to be
provided by implementors of the protocol.

Example (shortened and simplified to just show the relevant parts):

public protocol StandardGraphTraversal {
// push() and pop() are implementation details and not part of the API
protected func push(vertex: Vertex)
protected func pop() -> Vertex?

public func next() -> Vertex?
}

public extension StandardGraphTraversal {

// leaving out things like visitor callbacks and coloring the visited
vertices
public func next() -> Vertex? {
guard let source = pop() else { // using pop() here
return nil
}
for target in graph.neighborsOf(source) {
if shouldFollowVertex(target) {
push(vertex) // using push() here
}
}
}
}

// supplying the implementation detail: using a queue results in breadth
first traversal
public struct BreadthFirstTraversal : StandardGraphTraversal {
var queue: Queue<Vertex> = Queue()

protected func push(vertex: Vertex) { return queue.push(vertex) }
protected func pop() -> Vertex? { return queue.pop() }
}

// supplying the implementation detail: using a stack results in depth
first traversal
public struct DepthFirstTraversal : StandardGraphTraversal {
var stack: Stack<Vertex> = Stack()

protected func push(vertex: Vertex) { return stack.push(vertex) }
protected func pop() -> Vertex? { return stack.pop() }
}

Currently I cannot express that in Swift and have to make push() and pop()
public :frowning:

(Allowing `internal` in public protocols would be sufficient in my example
but would not help in more general cases where the implementors would have
to be provided in another module by the framework user).

.NET has a good solution that respects the OOP terminology and deals with
accessibility in modules and at the API level:
https://msdn.microsoft.com/en-us/library/wxh6fsc7.aspx

`public` and `internal` seem to be quite the same as in Swift.
`private` is based on the type instead of the file. I think Swift’s file
based `private` is better because it allows to express the same by putting
a type into its file alone and allows to create strongly interdependent
types by putting them together into the same file.
`protected` is what I’m missing as well
`protected internal` seems to be the union of `protected` and `internal`.
I think that is not needed and is probably there to allow testing for which
Swift has a nicer solution with @testable imports.

In short the only thing which is missing IMO is `protected`.

-Thorsten

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


(Taras Zakharko) #15

That is true of course, but explicitly modelling complex dependency relationships between scopes can be tedious. If you make private truly private, then you also need concepts such a friends and many some others in order to write efficient code. File- and module-based access offers a decent compromise by allowing you to put interdependent implementations into a single file. Personally, I think that this is an elegant and reasonable solution. Yes, it does trade formalism for convenience, but I believe that the tradeoff is reasonable. You already need to maintain some degree of discipline when writing code (you can’t make the compiler to enforce all conventions for you), and I don’t think that its such a big deal to ask people to be careful when modifying a file with a pre-existing type implementation. In the end, your argumentation mainly applies to the case when you have multiple type implementation in the same file. Which is bad style in the first place. Once you embrace idea that all declarations in a single file are closely interdependent (this way or another), file-based access control stops being an issue as you present it.

Best,

Taras

···

On 25 Jan 2016, at 14:45, Ilya Belenkiy <ilya.belenkiy@gmail.com> wrote:

Formal means enforceable by the compiler. File based access control is useful as well but not as a substitute for real data encapsulation.

On Mon, Jan 25, 2016 at 8:13 AM Taras Zakharko <taras.zakharko@googlemail.com <mailto:taras.zakharko@googlemail.com>> wrote:
I disagree with Ilya on his stance on ‘private’. I do understand the argumentation, but the reasoning seems overly formal to me and the classical solution (private visible only within the class) also comes with a number of drawbacks that result in things like friends declarations in C++. It is nice to have relationships between entities explicit, but one can go overboard with the rigidity.

However, I also strongly agree with Thorsten that a subclass (but not public) visible modifier has a lot of uses. I would prefer to have four levels of access:

private (file scope)
internal (module scope)
protected (subclass scope)
public

Best,

Taras

On 24 Jan 2016, at 09:44, Thorsten Seitz via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Am 23.01.2016 um 16:56 schrieb Ilya Belenkiy via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

I hope that access control can be revisited. It is the number one complaint about Swift that I hear from experienced developers. The current solution came as a complete surprise to every ObjC developer I've talked to. The natural expectation was that a strong access control system would be part of a strong type system. I already submitted a pull request with a proposal for this after a lengthy discussion here, but so far it was ignored. I hope that this can be revisited. Even if most people who responded earlier in this list decided that they were happy with the current state, it represents a tiny fraction of people using the language, and at least all the people I talked to strongly disagree but just aren’t on the list.

Right now access control is file based and not API based. This is much easier to implement but useless to express that certain elements of a class are implementation details that are not meant to be used anywhere else (someone can add more code to the same file and get access to the implementation details without modifying the class).

I think the file based approach for `private` has advantages because it allows putting several things that have to know each other deeply into the same file (C++ would use friend access for that).

It’s also impossible to hide APIs that are meant only for customization points of subclasses.

Here I agree. What I am really missing is the ability to declare something „protected“ in a protocol (or class) which is not part of the public API but which I then can use in its default implementation but which has to be provided by implementors of the protocol.

Example (shortened and simplified to just show the relevant parts):

public protocol StandardGraphTraversal {
  // push() and pop() are implementation details and not part of the API
  protected func push(vertex: Vertex)
  protected func pop() -> Vertex?

  public func next() -> Vertex?
}

public extension StandardGraphTraversal {

  // leaving out things like visitor callbacks and coloring the visited vertices
  public func next() -> Vertex? {
    guard let source = pop() else { // using pop() here
      return nil
    }
    for target in graph.neighborsOf(source) {
      if shouldFollowVertex(target) {
        push(vertex) // using push() here
      }
    }
  }
}

// supplying the implementation detail: using a queue results in breadth first traversal
public struct BreadthFirstTraversal : StandardGraphTraversal {
  var queue: Queue<Vertex> = Queue()

  protected func push(vertex: Vertex) { return queue.push(vertex) }
  protected func pop() -> Vertex? { return queue.pop() }
}

// supplying the implementation detail: using a stack results in depth first traversal
public struct DepthFirstTraversal : StandardGraphTraversal {
  var stack: Stack<Vertex> = Stack()

  protected func push(vertex: Vertex) { return stack.push(vertex) }
  protected func pop() -> Vertex? { return stack.pop() }
}
  
Currently I cannot express that in Swift and have to make push() and pop() public :frowning:

(Allowing `internal` in public protocols would be sufficient in my example but would not help in more general cases where the implementors would have to be provided in another module by the framework user).

.NET has a good solution that respects the OOP terminology and deals with accessibility in modules and at the API level:
https://msdn.microsoft.com/en-us/library/wxh6fsc7.aspx

`public` and `internal` seem to be quite the same as in Swift.
`private` is based on the type instead of the file. I think Swift’s file based `private` is better because it allows to express the same by putting a type into its file alone and allows to create strongly interdependent types by putting them together into the same file.
`protected` is what I’m missing as well
`protected internal` seems to be the union of `protected` and `internal`. I think that is not needed and is probably there to allow testing for which Swift has a nicer solution with @testable imports.

In short the only thing which is missing IMO is `protected`.

-Thorsten

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


(Dave Abrahams) #16

Yes, but that understanding doesn't need to be as deep.

Can you explain that statement? I don't see how it can possibly be
true.

Also, with The API based approach, the compiler can enforce the class
author's intent, and the intent is very clearly spelled out.

I don't see how that's a difference in Swift. If you, as a class
author, don't have control over the contents of the file your class is
defined it, other people can break your class. If you do, you can
prevent them from breaking it.

···

on Sat Jan 23 2016, Ilya Belenkiy <ilya.belenkiy-AT-gmail.com> wrote:

On Sat, Jan 23, 2016 at 5:20 PM Joe Groff <jgroff@apple.com> wrote:

On Jan 23, 2016, at 2:14 PM, Ilya Belenkiy via swift-evolution < >> swift-evolution@swift.org> wrote:

Anyone who can "add more code to the same file" can just as easily
modify/extend the class.

Yes, but with an API based access control, anyone modifying / extending
the class can instantly see the author’s intent from the API declaration,
and the compiler can prevent *accidental* misuse. Deliberate misuse cannot
be stopped no matter what access control is in place (as long as the coder
can modify the code in the module). But at least with an API based access
control, a deliberate misuse is very easy to spot when debugging /
reviewing the code. With a file based access control, the only way to tell
if an API is really private or can be safely used anywhere in the same file
is to read the comments / source code. And it’s much easier to miss in
maintenance, debugging, or code reviews. The only way to make it obvious in
the current model is to adopt a convention like _ in front of methods.

There's nothing about extending a class that ought to raise a red flag in
Swift, because it's perfectly legit to do so and use only the class'
public APIs.

Unless the programmer extends a class in the same file. Then he can call
anything marked private, and it’s impossible to tell without reading the
code whether it violates the class invariants. There is no way to express
this in Swift today. My proposal for local / scoped access control would
solve this.

This doesn't change if you constrain access to a class's members to the
primary class declaration. Anyone extending the class within the 'class Foo
{ }' braces also has to understand the class invariants.

-Joe

we didn't do file-level access control because it was easier; we did it
because we thought it was a better model for Swift, where types are
liberally open for extension.

That’s what I assumed until I saw someone say it in this list when someone
else raised a similar concern. Types in C++ and C# are also liberally open
for extension, but they provide much better support for expressing the
intent of an API and protect against accidental misuse.

On Jan 23, 2016, at 3:45 PM, Dave Abrahams via swift-evolution < >> swift-evolution@swift.org> wrote:

on Sat Jan 23 2016, Ilya Belenkiy <swift-evolution@swift.org> wrote:

I hope that access control can be revisited. It is the number one
complaint about Swift that I hear from experienced developers. The
current solution came as a complete surprise to every ObjC developer
I've talked to. The natural expectation was that a strong access
control system would be part of a strong type system. I already
submitted a pull request with a proposal for this after a lengthy
discussion here, but so far it was ignored. I hope that this can be
revisited. Even if most people who responded earlier in this list
decided that they were happy with the current state, it represents a
tiny fraction of people using the language, and at least all the
people I talked to strongly disagree but just aren’t on the list.

Right now access control is file based and not API based. This is much
easier to implement but useless to express that certain elements of a
class are implementation details that are not meant to be used
anywhere else (someone can add more code to the same file and get
access to the implementation details without modifying the
class).

I agree that our current access control has real weaknesses, but I don't
agree you've identified one above. Anyone who can "add more code to the
same file" can just as easily modify/extend the class. There's nothing
about extending a class that ought to raise a red flag in Swift, because
it's perfectly legit to do so and use only the class' public APIs.

I'm going to let others respond to the rest of this in more detail, but
I should also add that we didn't do file-level access control because it
was easier; we did it because we thought it was a better model for
Swift, where types are liberally open for extension.

It’s also impossible to hide APIs that are meant only for
customization points of subclasses.

Some of these problems could be solved with a convention like putting
_ in front of protected and __ in front of private methods, and Cocoa
uses a similar approach today. But this “solution” is in the same
category as using prefixes to determine types. If Swift aims to have a
strong type system, and it’s described as one of advantages of Swift
over ObjC, it should help enforce this at the language level.

.NET has a good solution that respects the OOP terminology and deals
with accessibility in modules and at the API level:
https://msdn.microsoft.com/en-us/library/wxh6fsc7.aspx

I think that a similar approach would work much better in Swift.


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

Cheers,

--
-Dave

_______________________________________________
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

--
-Dave


(Curt Clifton) #17

It might aid the discussion if you provided a link to your proposal. I'm assuming it includes examples of problems that your proposed changes solve. I'd be interested in seeing those.

Cheers,

Curt

···

-------------------------
Curt Clifton, PhD
Software Developer
The Omni Group
www.curtclifton.net

On Jan 23, 2016, at 6:17 PM, Ilya Belenkiy via swift-evolution <swift-evolution@swift.org> wrote:

Yes, but with an API based access control,

I don't know what that term means, sorry.

I had to come up with this term to distinguish it from a file based access control. In API based access control, visibility of anything is tied to the declaration instead of the file it is in. It’s the model that C++ and C# ( and I am sure many others) use. In file based access control, visibility of anything is tied to the file it is in and has little to do with the class API itself. It’s the model that Swift currently follows.

anyone modifying / extending the class can instantly see the author’s
intent from the API declaration,

I don't see how that's different from what we have in Swift. Private
APIs are the ones you don't want anyone to touch because they can break
your invariants.

In Swift, any additional code in the same source file can use the APIs declared private and break your invariants. The semantic meaning of private is not “use only inside the class” but “use only inside this file”. It is currently impossible to express “use only inside the class” semantics.

Seeing the keyword “private” isn't enough for you in Swift, but it is in
C++? Why?

Because in Swift, private means “use only inside this file” and not “use only inside this class / scope”. In C++ private means “use only inside this class”. Moreover, C++ puts a special emphasis on this — variables are private inside the class unless the code specifically says otherwise.

Defining one type per file is usually a good practice anyway.

This really depends on the context. There is value in keeping similar concepts / implementations in the same file. If one class and one extension per file become a requirement, then private will have the same semantics as in C++. It will also become very inconvenient to write code because even a small extension with 1 method that can fit on one line will require a separate file.

Without prohibiting class extensions from being made in the same file
where the class is defined—a capability we want to keep available to
class authors—that will always be the case.

It doesn’t have to be. My proposal (still a pull request) provides a way to explicitly declare a function or a property to be private to the scope where it is defined. So extensions and subclasses declared in the same file cannot access it and break the invariant. If it’s applied to both class definitions and extensions, it’s very consistent and provides an explicit declaration of intent that can (and should) be enforced by the compiler.

Therefore, you have the
same scenario as in C++: anyone who modifies the file where a class is
defined might be violating its invariants. It's just that in C++ the
violator has to write code between a particular set of braces. When
reviewing diffs it's very common not to have enough context to see those
braces anyway.

This is a crucial distinction. That particular set of braces defines a scope which acts as a black box and hides the implementation details. Anything outside the scope cannot damage the internal state, and the compiler *enforces* it. In Swift, the compiler only enforces that the API is not visible from another file because the language has no way to express “this should be visible only in the scope to hide implementation details”. The only exception to that are functions inside other functions and local variables — right now this is the only way to completely hide implementation details, but it’s very limited.

My main 2 points are that it’s impossible to express the intent of a truly local access level to hide implementation details, and because of that, the compiler cannot enforce this intent.

When
reviewing diffs it's very common not to have enough context to see those
braces anyway.

This depends on how thoroughly the code is reviewed. But the main point there is that when you find out, you know that whoever worked around a private API did so knowing that he was introducing a hack. Because Swift has no way of expressing “this is private to the class, not just file it’s in”, someone may not know and make a mistake, and the compiler cannot catch it. My proposal makes the intent expressible in the language and enforceable by the compiler.

Not at all in the same way. In C++, you can't add methods to a class
outside the file where it's declared unless there's some really horrible
preprocessor stuff going on, and even then the class author would have
had to explicitly left that door open for you.

True, but my proposal deals with extensions the same way it does with class definitions — it provides a way to hide implementation details in a scope (whether it be a class definition or an extension scope) and make them invisible to everything else including extensions or other extensions or anything else in the same file. It provides a way to express the intent in the language and enforce it by the compiler.

On Jan 23, 2016, at 5:43 PM, Dave Abrahams <dabrahams@apple.com> wrote:

on Sat Jan 23 2016, Ilya Belenkiy <ilya.belenkiy-AT-gmail.com> wrote:

Anyone who can "add more code to the same file" can just as easily modify/extend the class.

Yes, but with an API based access control,

I don't know what that term means, sorry.

anyone modifying / extending the class can instantly see the author’s
intent from the API declaration,

I don't see how that's different from what we have in Swift. Private
APIs are the ones you don't want anyone to touch because they can break
your invariants.

and the compiler can prevent *accidental* misuse. Deliberate misuse
cannot be stopped no matter what access control is in place (as long
as the coder can modify the code in the module). But at least with an
API based access control, a deliberate misuse is very easy to spot
when debugging / reviewing the code. With a file based access control,
the only way to tell if an API is really private or can be safely used
anywhere in the same file is to read the comments / source code.

Seeing the keyword “private” isn't enough for you in Swift, but it is in
C++? Why?

And it’s much easier to miss in maintenance, debugging, or code
reviews. The only way to make it obvious in the current model is to
adopt a convention like _ in front of methods.

The only reason we're doing that in the standard library is that we're
currently forced to make some implementation details formally public,
and we can't use “private” because <reasons>. If it weren't for these
limitations, we'd be very happy with “private.” Defining one type per
file is usually a good practice anyway.

There's nothing about extending a class that ought to raise a red
flag in Swift, because it's perfectly legit to do so and use only
the class' public APIs.

Unless the programmer extends a class in the same file. Then he can
call anything marked private, and it’s impossible to tell without
reading the code whether it violates the class invariants.

Without prohibiting class extensions from being made in the same file
where the class is defined—a capability we want to keep available to
class authors—that will always be the case. Therefore, you have the
same scenario as in C++: anyone who modifies the file where a class is
defined might be violating its invariants. It's just that in C++ the
violator has to write code between a particular set of braces. When
reviewing diffs it's very common not to have enough context to see those
braces anyway.

There is no way to express this in Swift today. My proposal for local
/ scoped access control would solve this.

we didn't do file-level access control because it was easier; we
did it because we thought it was a better model for Swift, where
types are liberally open for extension.

That’s what I assumed until I saw someone say it in this list when
someone else raised a similar concern. Types in C++ and C# are also
liberally open for extension,

Not at all in the same way. In C++, you can't add methods to a class
outside the file where it's declared unless there's some really horrible
preprocessor stuff going on, and even then the class author would have
had to explicitly left that door open for you.

--
-Dave

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


(Dave Abrahams) #18

Yes, but with an API based access control,

I don't know what that term means, sorry.

I had to come up with this term to distinguish it from a file based
access control. In API based access control, visibility of anything is
tied to the declaration instead of the file it is in.

It’s the model that C++ and C# ( and I am sure many others) use. In
file based access control, visibility of anything is tied to the file
it is in and has little to do with the class API itself. It’s the
model that Swift currently follows.

I disagree with your analysis. Every declaration in Swift has a
visibility. That visibility is attached to the declaration itself, not
to the file. Every declaration can live in only one spot, and that's
where the visibility is set. You can't go to another file and do
something there to alter the declaration's visibility. The fact that we
made the most restrictive visibilty level "visibile within the file"
rather than "visible within the type" does not mean the model is
fundamentally different.

anyone modifying / extending the class can instantly see the author’s
intent from the API declaration,

I don't see how that's different from what we have in Swift. Private
APIs are the ones you don't want anyone to touch because they can break
your invariants.

In Swift, any additional code in the same source file can use the APIs
declared private and break your invariants. The semantic meaning of
private is not “use only inside the class” but “use only inside this
file”. It is currently impossible to express “use only inside the
class” semantics.

That is true. The question is, would having a way to express that be
worth the complexity it introduces? Reasonable people can disagree
about that.

Seeing the keyword “private” isn't enough for you in Swift, but it is in
C++? Why?

Because in Swift, private means “use only inside this file” and not
“use only inside this class / scope”. In C++ private means “use only
inside this class”. Moreover, C++ puts a special emphasis on this —
variables are private inside the class unless the code specifically
says otherwise.

I ended up generally being explicit about scoping in C++ anyway: The
most-accessible (public) part of a C++ class declaration should appear
at the top, so you end up with everything thereafter being public unless
you go out of your way to explicitly specify access.

Defining one type per file is usually a good practice anyway.

This really depends on the context. There is value in keeping similar
concepts / implementations in the same file.

Usually when they have an intimate relationship and need access to one
another's implementation details. We didn't want to end up with
"friend" as in C++, so we made file-level access control what you get
from "private."

For what it's worth, my background, up until a few years ago, is as a
C++ programmer. I was a little uncomfortable at first with the idea of
giving up a "limit-to-the-type" level of access control, but when I
considered how real C++ code was developed I realized that people
generally control changes to code at file-level granularity. If they
want protection from themselves, they can choose the file boundaries
accordingly. IMO it's a good trade to avoid having a wart like "friend"
in the language. Another level of access control might be useful, but
I don't think it's crucial.

If one class and one extension per file become a requirement,
then private will have the same semantics as in C++. It will also
become very inconvenient to write code because even a small extension
with 1 method that can fit on one line will require a separate file.

Whoa, I never said anything about one extension per file, or about
requirements.

Without prohibiting class extensions from being made in the same file
where the class is defined—a capability we want to keep available to
class authors—that will always be the case.

It doesn’t have to be. My proposal (still a pull request) provides a
way to explicitly declare a function or a property to be private to
the scope where it is defined. So extensions and subclasses declared
in the same file cannot access it and break the invariant. If it’s
applied to both class definitions and extensions, it’s very consistent
and provides an explicit declaration of intent that can (and should)
be enforced by the compiler.

Therefore, you have the
same scenario as in C++: anyone who modifies the file where a class is
defined might be violating its invariants. It's just that in C++ the
violator has to write code between a particular set of braces. When
reviewing diffs it's very common not to have enough context to see those
braces anyway.

This is a crucial distinction. That particular set of braces defines a
scope which acts as a black box and hides the implementation
details. Anything outside the scope cannot damage the internal state,
and the compiler *enforces* it.

We do that in Swift as well; we just chose a different box boundary.

In Swift, the compiler only enforces that the API is not visible from
another file because the language has no way to express “this should
be visible only in the scope to hide implementation details”. The only
exception to that are functions inside other functions and local
variables — right now this is the only way to completely hide
implementation details, but it’s very limited.

My main 2 points are that it’s impossible to express the intent of a
truly local access level to hide implementation details, and because
of that, the compiler cannot enforce this intent.

When reviewing diffs it's very common not to have enough context to
see those braces anyway.

This depends on how thoroughly the code is reviewed. But the main
point there is that when you find out, you know that whoever worked
around a private API did so knowing that he was introducing a
hack. Because Swift has no way of expressing “this is private to the
class, not just file it’s in”, someone may not know and make a
mistake, and the compiler cannot catch it. My proposal makes the
intent expressible in the language and enforceable by the compiler.

I think you'll agree that if you ignore (or are ignorant of) the intent
behind the application of any given access control construct, and you
have the ability to change all the code in the project, you are likely
to break someone's invariants. If you aren't willing to treat something
that's labelled as "private" as "don't touch unless you know what you're
doing," there's nothing the compiler can do to enforce correctness, and
there's not much difference between making make changes in the file
where it's declared and making changes between the braces where it's
declared.

Not at all in the same way. In C++, you can't add methods to a class
outside the file where it's declared unless there's some really horrible
preprocessor stuff going on, and even then the class author would have
had to explicitly left that door open for you.

True, but my proposal deals with extensions the same way it does with
class definitions — it provides a way to hide implementation details
in a scope (whether it be a class definition or an extension scope)
and make them invisible to everything else including extensions or
other extensions or anything else in the same file. It provides a way
to express the intent in the language and enforce it by the compiler.

I think it's possible to believe that your feature would be useful
without also believing there's something fundamentally wrong with the
current model, and your arguments make it sound to me like you think
you're getting a lot more certainty from C++'s "private" than you
actually are.

···

on Sat Jan 23 2016, Ilya Belenkiy <swift-evolution@swift.org> wrote:

--
-Dave


(Ilya Belenkiy) #19

Here is a link to the pull request:

https://github.com/apple/swift-evolution/pull/64

···

On Jan 23, 2016, at 9:23 PM, Curt Clifton <curt@omnigroup.com> wrote:

It might aid the discussion if you provided a link to your proposal. I'm assuming it includes examples of problems that your proposed changes solve. I'd be interested in seeing those.

Cheers,

Curt
-------------------------
Curt Clifton, PhD
Software Developer
The Omni Group
www.curtclifton.net

On Jan 23, 2016, at 6:17 PM, Ilya Belenkiy via swift-evolution <swift-evolution@swift.org> wrote:

Yes, but with an API based access control,

I don't know what that term means, sorry.

I had to come up with this term to distinguish it from a file based access control. In API based access control, visibility of anything is tied to the declaration instead of the file it is in. It’s the model that C++ and C# ( and I am sure many others) use. In file based access control, visibility of anything is tied to the file it is in and has little to do with the class API itself. It’s the model that Swift currently follows.

anyone modifying / extending the class can instantly see the author’s
intent from the API declaration,

I don't see how that's different from what we have in Swift. Private
APIs are the ones you don't want anyone to touch because they can break
your invariants.

In Swift, any additional code in the same source file can use the APIs declared private and break your invariants. The semantic meaning of private is not “use only inside the class” but “use only inside this file”. It is currently impossible to express “use only inside the class” semantics.

Seeing the keyword “private” isn't enough for you in Swift, but it is in
C++? Why?

Because in Swift, private means “use only inside this file” and not “use only inside this class / scope”. In C++ private means “use only inside this class”. Moreover, C++ puts a special emphasis on this — variables are private inside the class unless the code specifically says otherwise.

Defining one type per file is usually a good practice anyway.

This really depends on the context. There is value in keeping similar concepts / implementations in the same file. If one class and one extension per file become a requirement, then private will have the same semantics as in C++. It will also become very inconvenient to write code because even a small extension with 1 method that can fit on one line will require a separate file.

Without prohibiting class extensions from being made in the same file
where the class is defined—a capability we want to keep available to
class authors—that will always be the case.

It doesn’t have to be. My proposal (still a pull request) provides a way to explicitly declare a function or a property to be private to the scope where it is defined. So extensions and subclasses declared in the same file cannot access it and break the invariant. If it’s applied to both class definitions and extensions, it’s very consistent and provides an explicit declaration of intent that can (and should) be enforced by the compiler.

Therefore, you have the
same scenario as in C++: anyone who modifies the file where a class is
defined might be violating its invariants. It's just that in C++ the
violator has to write code between a particular set of braces. When
reviewing diffs it's very common not to have enough context to see those
braces anyway.

This is a crucial distinction. That particular set of braces defines a scope which acts as a black box and hides the implementation details. Anything outside the scope cannot damage the internal state, and the compiler *enforces* it. In Swift, the compiler only enforces that the API is not visible from another file because the language has no way to express “this should be visible only in the scope to hide implementation details”. The only exception to that are functions inside other functions and local variables — right now this is the only way to completely hide implementation details, but it’s very limited.

My main 2 points are that it’s impossible to express the intent of a truly local access level to hide implementation details, and because of that, the compiler cannot enforce this intent.

When
reviewing diffs it's very common not to have enough context to see those
braces anyway.

This depends on how thoroughly the code is reviewed. But the main point there is that when you find out, you know that whoever worked around a private API did so knowing that he was introducing a hack. Because Swift has no way of expressing “this is private to the class, not just file it’s in”, someone may not know and make a mistake, and the compiler cannot catch it. My proposal makes the intent expressible in the language and enforceable by the compiler.

Not at all in the same way. In C++, you can't add methods to a class
outside the file where it's declared unless there's some really horrible
preprocessor stuff going on, and even then the class author would have
had to explicitly left that door open for you.

True, but my proposal deals with extensions the same way it does with class definitions — it provides a way to hide implementation details in a scope (whether it be a class definition or an extension scope) and make them invisible to everything else including extensions or other extensions or anything else in the same file. It provides a way to express the intent in the language and enforce it by the compiler.

On Jan 23, 2016, at 5:43 PM, Dave Abrahams <dabrahams@apple.com> wrote:

on Sat Jan 23 2016, Ilya Belenkiy <ilya.belenkiy-AT-gmail.com> wrote:

Anyone who can "add more code to the same file" can just as easily modify/extend the class.

Yes, but with an API based access control,

I don't know what that term means, sorry.

anyone modifying / extending the class can instantly see the author’s
intent from the API declaration,

I don't see how that's different from what we have in Swift. Private
APIs are the ones you don't want anyone to touch because they can break
your invariants.

and the compiler can prevent *accidental* misuse. Deliberate misuse
cannot be stopped no matter what access control is in place (as long
as the coder can modify the code in the module). But at least with an
API based access control, a deliberate misuse is very easy to spot
when debugging / reviewing the code. With a file based access control,
the only way to tell if an API is really private or can be safely used
anywhere in the same file is to read the comments / source code.

Seeing the keyword “private” isn't enough for you in Swift, but it is in
C++? Why?

And it’s much easier to miss in maintenance, debugging, or code
reviews. The only way to make it obvious in the current model is to
adopt a convention like _ in front of methods.

The only reason we're doing that in the standard library is that we're
currently forced to make some implementation details formally public,
and we can't use “private” because <reasons>. If it weren't for these
limitations, we'd be very happy with “private.” Defining one type per
file is usually a good practice anyway.

There's nothing about extending a class that ought to raise a red
flag in Swift, because it's perfectly legit to do so and use only
the class' public APIs.

Unless the programmer extends a class in the same file. Then he can
call anything marked private, and it’s impossible to tell without
reading the code whether it violates the class invariants.

Without prohibiting class extensions from being made in the same file
where the class is defined—a capability we want to keep available to
class authors—that will always be the case. Therefore, you have the
same scenario as in C++: anyone who modifies the file where a class is
defined might be violating its invariants. It's just that in C++ the
violator has to write code between a particular set of braces. When
reviewing diffs it's very common not to have enough context to see those
braces anyway.

There is no way to express this in Swift today. My proposal for local
/ scoped access control would solve this.

we didn't do file-level access control because it was easier; we
did it because we thought it was a better model for Swift, where
types are liberally open for extension.

That’s what I assumed until I saw someone say it in this list when
someone else raised a similar concern. Types in C++ and C# are also
liberally open for extension,

Not at all in the same way. In C++, you can't add methods to a class
outside the file where it's declared unless there's some really horrible
preprocessor stuff going on, and even then the class author would have
had to explicitly left that door open for you.

--
-Dave

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


(Ilya Belenkiy) #20

I think it's possible to believe that your feature would be useful
without also believing there's something fundamentally wrong with the
current model, and your arguments make it sound to me like you think
you're getting a lot more certainty from C++'s "private" than you
actually are.

Data encapsulation is a cornerstone of object oriented programming. Swift has classes, and yet it is impossible to completely hide state / implementation details inside the class. Moreover, it’s impossible to express that something is intended to be completely hidden. Since such a fundamental idea is not supported, there is something fundamentally wrong with the current model. Swift is already a great language, and this issue aside, it’s by far the best language I’ve used. In all other areas Swift does a great job to help the programmer write correct code. This is one weird and very noticeable deviation.

We do that in Swift as well; we just chose a different box boundary.

The box boundary is not arbitrary. The current model is perfect for code without classes. But for classes, data encapsulation is not just a “nice to have” feature. It’s such a fundamental part of object oriented programming that it’s expected. This would be like having no functions in a functional language or no variables in a procedural language :–)

Whoa, I never said anything about one extension per file, or about
requirements.

This would be the only way to make the current meaning of private perfectly match what everybody outside of Swift expects it to mean.

That is true. The question is, would having a way to express that be
worth the complexity it introduces? Reasonable people can disagree
about that.

I think that it’s worth every bit. I can live without “protected”. It can be argued that once you expose something, you lose control over it. But “private” is different. I’ll reference data encapsulation again. I don’t understand how anyone can seriously argue that it’s not important in a language that aims to support object oriented programming.

Usually when they have an intimate relationship and need access to one
another's implementation details. We didn't want to end up with
"friend" as in C++, so we made file-level access control what you get
from "private.”

Yes, I totally agree that having a way to say “available to all in this file” is useful precisely for this reason. I just think that it’s misnamed. The honest way to call it would be something like “file internal”. But even if the right name is already taken, we can still have the access level itself. I proposed “local” or “scoped”. (I’d prefer to rename private to file internal and call local “private”, but that raises backward compatibility issues, and I didn’t want the proposal to be rejected just for that reason.)

For what it's worth, my background, up until a few years ago, is as a
C++ programmer. I was a little uncomfortable at first with the idea of
giving up a "limit-to-the-type" level of access control, but when I
considered how real C++ code was developed I realized that people
generally control changes to code at file-level granularity. If they
want protection from themselves, they can choose the file boundaries
accordingly. IMO it's a good trade to avoid having a wart like "friend"
in the language. Another level of access control might be useful, but
I don't think it's crucial.

This may be true, but the right solution is not do abandon data encapsulation. It’s to make it easy to use correctly. I always used const in C++ wherever possible, but it was a pain. Swift implementation is perfect. Very easy to use and encourages good practice. I don’t see why the same couldn’t be done with access control. (I think that making “local” the default access level would be a perfect solution, but simply having the option would be good enough for me. I’d use it everywhere to hide internal state and implementation details.)

···

On Jan 23, 2016, at 10:01 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

on Sat Jan 23 2016, Ilya Belenkiy <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Yes, but with an API based access control,

I don't know what that term means, sorry.

I had to come up with this term to distinguish it from a file based
access control. In API based access control, visibility of anything is
tied to the declaration instead of the file it is in.

It’s the model that C++ and C# ( and I am sure many others) use. In
file based access control, visibility of anything is tied to the file
it is in and has little to do with the class API itself. It’s the
model that Swift currently follows.

I disagree with your analysis. Every declaration in Swift has a
visibility. That visibility is attached to the declaration itself, not
to the file. Every declaration can live in only one spot, and that's
where the visibility is set. You can't go to another file and do
something there to alter the declaration's visibility. The fact that we
made the most restrictive visibilty level "visibile within the file"
rather than "visible within the type" does not mean the model is
fundamentally different.

anyone modifying / extending the class can instantly see the author’s
intent from the API declaration,

I don't see how that's different from what we have in Swift. Private
APIs are the ones you don't want anyone to touch because they can break
your invariants.

In Swift, any additional code in the same source file can use the APIs
declared private and break your invariants. The semantic meaning of
private is not “use only inside the class” but “use only inside this
file”. It is currently impossible to express “use only inside the
class” semantics.

That is true. The question is, would having a way to express that be
worth the complexity it introduces? Reasonable people can disagree
about that.

Seeing the keyword “private” isn't enough for you in Swift, but it is in
C++? Why?

Because in Swift, private means “use only inside this file” and not
“use only inside this class / scope”. In C++ private means “use only
inside this class”. Moreover, C++ puts a special emphasis on this —
variables are private inside the class unless the code specifically
says otherwise.

I ended up generally being explicit about scoping in C++ anyway: The
most-accessible (public) part of a C++ class declaration should appear
at the top, so you end up with everything thereafter being public unless
you go out of your way to explicitly specify access.

Defining one type per file is usually a good practice anyway.

This really depends on the context. There is value in keeping similar
concepts / implementations in the same file.

Usually when they have an intimate relationship and need access to one
another's implementation details. We didn't want to end up with
"friend" as in C++, so we made file-level access control what you get
from "private."

For what it's worth, my background, up until a few years ago, is as a
C++ programmer. I was a little uncomfortable at first with the idea of
giving up a "limit-to-the-type" level of access control, but when I
considered how real C++ code was developed I realized that people
generally control changes to code at file-level granularity. If they
want protection from themselves, they can choose the file boundaries
accordingly. IMO it's a good trade to avoid having a wart like "friend"
in the language. Another level of access control might be useful, but
I don't think it's crucial.

If one class and one extension per file become a requirement,
then private will have the same semantics as in C++. It will also
become very inconvenient to write code because even a small extension
with 1 method that can fit on one line will require a separate file.

Whoa, I never said anything about one extension per file, or about
requirements.

Without prohibiting class extensions from being made in the same file
where the class is defined—a capability we want to keep available to
class authors—that will always be the case.

It doesn’t have to be. My proposal (still a pull request) provides a
way to explicitly declare a function or a property to be private to
the scope where it is defined. So extensions and subclasses declared
in the same file cannot access it and break the invariant. If it’s
applied to both class definitions and extensions, it’s very consistent
and provides an explicit declaration of intent that can (and should)
be enforced by the compiler.

Therefore, you have the
same scenario as in C++: anyone who modifies the file where a class is
defined might be violating its invariants. It's just that in C++ the
violator has to write code between a particular set of braces. When
reviewing diffs it's very common not to have enough context to see those
braces anyway.

This is a crucial distinction. That particular set of braces defines a
scope which acts as a black box and hides the implementation
details. Anything outside the scope cannot damage the internal state,
and the compiler *enforces* it.

We do that in Swift as well; we just chose a different box boundary.

In Swift, the compiler only enforces that the API is not visible from
another file because the language has no way to express “this should
be visible only in the scope to hide implementation details”. The only
exception to that are functions inside other functions and local
variables — right now this is the only way to completely hide
implementation details, but it’s very limited.

My main 2 points are that it’s impossible to express the intent of a
truly local access level to hide implementation details, and because
of that, the compiler cannot enforce this intent.

When reviewing diffs it's very common not to have enough context to
see those braces anyway.

This depends on how thoroughly the code is reviewed. But the main
point there is that when you find out, you know that whoever worked
around a private API did so knowing that he was introducing a
hack. Because Swift has no way of expressing “this is private to the
class, not just file it’s in”, someone may not know and make a
mistake, and the compiler cannot catch it. My proposal makes the
intent expressible in the language and enforceable by the compiler.

I think you'll agree that if you ignore (or are ignorant of) the intent
behind the application of any given access control construct, and you
have the ability to change all the code in the project, you are likely
to break someone's invariants. If you aren't willing to treat something
that's labelled as "private" as "don't touch unless you know what you're
doing," there's nothing the compiler can do to enforce correctness, and
there's not much difference between making make changes in the file
where it's declared and making changes between the braces where it's
declared.

Not at all in the same way. In C++, you can't add methods to a class
outside the file where it's declared unless there's some really horrible
preprocessor stuff going on, and even then the class author would have
had to explicitly left that door open for you.

True, but my proposal deals with extensions the same way it does with
class definitions — it provides a way to hide implementation details
in a scope (whether it be a class definition or an extension scope)
and make them invisible to everything else including extensions or
other extensions or anything else in the same file. It provides a way
to express the intent in the language and enforce it by the compiler.

I think it's possible to believe that your feature would be useful
without also believing there's something fundamentally wrong with the
current model, and your arguments make it sound to me like you think
you're getting a lot more certainty from C++'s "private" than you
actually are.

--
-Dave

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