Proposal: refining Go export conventions
Sun, Apr 5, 2020 ❝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:
- managing type access through exported/unexported.
- 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.
- Fully public: exported function on an exported type.
- Package-restricted: exported function on an unexported type.
- Package-restricted: unexported function on an exported type.
- Package-restricted: unexported function on an unexported type.
Notice how there are three ways to define package-restricted functions?
Proposal:
- Use ‘unexported on unexported’ for private logic.
- Use ‘unexported on exported’ for private logic.
- Use ‘exported on unexported’ for package-accessible logic.
- Use ‘exported on exported’ for world-accessible logic.
- Now, in order to express mixed package-level and world-level access, one would need to use an interface. The interface is used to expose a subset of exported functions to the world.
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
- Gradually upgradeable from “private” to package-accessible to world-accessible.
- Define specific access semantic for each unique entry in the space.
- distinguish between fields allowed to be manipulated in package and other that aren’t
- Can be experimented with by means of convention. (
go vet
/golint
) - Reduce number of “discussions” due to multiple choice in implementation decisions.
Problems
- An interface is required to be able to mix world-accessible and package-accessible logic.
- It remains a convention, hence developers need to be aware or tooling needs to enforce.
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
- 2020-04-05 Additional section going into the interplay of access control on types with access control on functions, and comparing to Java.
- 2020-04-04 Minor additions to emphasize functions with receiver. Emphasize subjective nature of function access to type access. Minor tweaks.
- 2020-04-03 Initial version.