- Zero Initialization
- Generic Data Structures
- Specialized Generic Data Structures
- Implementation Hiding
- Type Composition
Implementation Hiding
28 type Stack interface { 29 Push(interface {}) 30 Pop() interface {} 31 } 32 func NewStack() Stack { 33 return &stack{} 34 }
From: genericStack.go
Interfaces in Go are exactly what their name implies. They define how you interact with a type, not how it is implemented. If users of your structure do not need to be able to access any of the fields, then it is good style to only export an interface exposing the public methods, rather than the structure itself.
The example at the start of this section shows a public interface for the two stack structures that we’ve defined already this chapter, along with a function for constructing it. By convention, the function that creates the concrete instance is named NewSomething(), Something is the name of the interface.
This is not the only way of hiding implementation details. Any structure member that does not start with a capital letter is automatically hidden, and is only accessible from within the package in which it is declared. As such, the structures that we’ve defined to implement the stacks are already hiding the details of their implementation: none of their fields is visible from other packages.
The correct approach to use depends on how you expect people to use your structures. Only exporting the interface gives you the most flexibility, because you can completely change any details of the implementation without altering code that uses it. You can even implement several different structures optimized for different use cases and return different ones depending on the use. On the other hand, this approach prevents people from allocating instances of your structure on the stack, and prevents you from using the zero initialization pattern.
Although Go does not support explicit stack allocation, the compiler will try to allocate structures on the stack as an implementation detail if they are short lived and do not have their address taken. This is very fast, as it just requires modifying the value of a register. If the object is allocated via a function, then the compiler is unable to do this, and will need to request memory from the garbage collector. For short-lived structures, this can be a significant performance penalty.