Proposal: refining Go export conventions

❝A proposal for Go coding conventions regarding access control.❞
Contents

Conventions for refined access control

The Go language is different in certain ways. One of these ways is the how access control is managed. This proposal builds on the type system and existing access control mechanism to increase the granularity of access control. As Go’s syntax is limited to ‘exported’/’unexported’, it cannot be enforced by syntax. However, it can still be applied by convention.

Available mechanisms:

  1. managing type access through exported/unexported.
  2. managing function access through exported/unexported.

Now, by combining the two, one can identify an extra level of granularity. The following applies to functions that have a receiver type, often called methods.

Notice how there are three ways to define package-restricted functions?

Proposal:

In this pair of function and type access, function access is subjective to type access. Type access determines whether the exported function is accessible to the world, or just members of the same package.

As said, strictly speaking this cannot be enforced. Using the same way of working w.r.t. access expectations helps multiple developers to write according to a consistent standard. An additional benefit is that Go’s static analysis tools already recognize exported functions on unexported types as not world-accessible (as expected). This is beneficial for recognizing functions that are only intended to be used within the same type.

This proposal assumes that there is more need for “private"-level logic, akin to Java’s private access modifier, than there is for a mix of package-accessible and world-accessible logic for a single type.

By using the ‘exported on unexported’ convention for package-level logic, it is easy to upgrade a type from package-accessible to world-accessible simply by exporting the type. All unexported ("private”) logic remains exactly as is with the same access expectations.

Effectively, the rule would be:

Use unexported methods (functions with receiver) for logic that is intended to be accessed only from within the same type.

Benefits

Problems

Considering the public interface

There may actually be benefit to not allow mixing of package-accessible and world-accessible functions. Let’s assume that we go with package-level access by default. Now when exposing functions to the world, one is forced to think about its interface, and make the corresponding effort to realize this. A lot of Java code is littered with public methods, while most of these methods offer nothing that warrants public access. In addition, you have the benefit of the interface being separate from the implementation by default.

Curiously, let’s consider Java’s access control mechanism for a moment. Java allows public methods on package-private classes. This actually does not make much sense. A package-private type cannot be accessed from outside the package, so public methods are only relevant if used with an interface anyways. So in a way, the additional access level does not fit with the other access options. Go’s use of exported/unexported on types and functions seems to result in a clearer separation of access control, even with similar expressiveness of access granularity.

Changelog