chandlerc · GitHub

We should decide how we want to mark public members of record types (structs or classes depending on the resolution of #651) vs. private members.

Lots of folks seem very interested in avoiding the use of a "region" the way C++ works because this can make it hard when jumping to declarations (using whatever tool, but potentially an IDE) to understand the access control.

Note: I'm not suggesting that we have exactly public and private (from C++) members. At least we need some analogy to protected from C++, and we may want something like "module internal" as well.

One suggestion that seems quite popular:

  • Make public the default, and have no annotation at all.
  • Require a local private annotation on any declaration that should be private
  • Similarly for any other non-public case: add a local annotation.

The result as an example here:

class Employee {
  // this would be public
  fn GetName() -> String;
  // this would be private
  private fn SerializeAsJSON() -> String;
  // this would be public
  fn GetID() -> Int;
}

The rationale for this particular set of rules is a bit surprising, so I'll try to walk through them informally here... Much of this is lifted from Discord Chat #syntax channel.

The reason to annotate the private members locally is to reduce the degree of distant and potentially difficult to locate context. @josh11b is proposing a principle in #646 to help codify the reason reducing the contextual load in general, and this seems like a reasonably compelling case to apply this principle.

The reason to make public members be the default is because their readability is a significantly higher priority -- the public API is what we would expect to be cited most and read most of everything. The implementation details (whether private as in C++ or a module-internal or even protected) have a lower readability cost of any annotations. Similarly, data members are often the most common things to be private, and are likely to have a lower readability hit from the annotation than functions as they are a simpler construct in general (no parameters, etc).

Making public the default isn't completely novel either: Kotlin does this as well

We could consider making an optional public specifier (which Kotlin does), but this seems prone to forming needless divergent styles and dialects of code. We couldn't teach people to expect a public specifier because not all code would use it. But especially coming from C++, that might be an easily made mistake.

So the suggestion is to have saying nothing, which we understand always as a very significant impact, to be the way of saying "public".

And following from this, we suggest that a similar approach should be adopted for the exported declarations of a Carbon library. This would move away from marking those explicitly with api and have it be the default. Here, it is important to note that we can reliably check that any declaration in an implementation file either matches an exported declaration in an API file or is correctly marked as internal (or however it ends up being spelled). Again, the goal here isn't to suggest the exact semantic set of markers, but rather the strategy of the public API being the default, and the implementation details carrying tho annotations, as the readability cost of those annotations is lower due to them not being part of the public API.

Read the original on github.com ↗