Bypassing Golang's lack of constructors
I've been writing Go for around a month now as for me it was a requirement that I get to learn it as a part of my new role at Sixt. One of the more interesting or frustrating aspects of the language, coming from a PHP background, is the ability to initialise objects in an invalid state.
Coming from an OO language, I am used to the ability to define constructors on objects - static methods that are an entrypoint into building an object. Without calling this, and without messing around with reflection, there is no other way to create one of these objects.
Golang doesn't provide constructors but instead advocates for a package-level factory method with the New
convention. If only this were then the singular way of initialising an object; unfortuntely this is not always necessarily the case.. and so embarks my internal struggle on 'the right way' of doing things in this programming language.
How can I emulate what I gained from constructors whilst adhering more towards "idiomatic go"?
"Constructors"
Firstly, let's define the goal. I want to provide a very simple object API to only allow third-party consumers to create an object in a valid state. Take a look at the following example that follows Golang's conventions:
type MyStruct struct { }
func NewMyStruct() MyStruct {
return MyStruct{dep: ADependency{}}
}
type ADependency struct { }
myStruct := NewMyStruct() // We get back a valid struct
This is great. We have a factory function by convention to create an instance of MyStruct. However, we can also do the following:
myStruct := MyStruct{}
Boom - we now have an instance of MyStruct
in an invalid state. Why does this matter?
type MyStruct struct {
dep *ADependency
}
func (m *MyStruct) DoSomethingWithDep() {
m.dep.DoSomething()
}
type ADependency struct {
num int
}
func (a *ADependency) DoSomething() {
fmt.Println(a.num)
}
(&MyStruct{}).DoSomethingWithDep()
The above gives us:
panic: runtime error: invalid memory address or nil pointer dereference
This is simply because ADependency
was not initialised. It's a nil. This problem exists in part because we are able to initialise this object in an invalid state, and in more complex codebases this can go unnoticed and blow up at runtime further down the line.
What can we do about this to ensure that it doesn't happen accidentally but in a consistent way?
Making things private
The reason we can call MyStruct{}
is because the type is exported from the package - meaning it's publically available for third parties (other packages) to use. To fix this, we can make it private:
/* Yep, now it's private because of the lower-cased 'm' */
type myStruct struct {
num int
}
func NewMyStruct() *myStruct {
return &myStruct{num: 42}
}
Now, from another package, we cannot call myStruct{}
. This struct's initialisation is only available via calling PackageName.NewMyStruct()
from other packages. This is great! However, this limitation does not exist from within the same package so you just need to make sure that you enforce (again, not by language design but by your own implementation) that you create this object correctly behind the veil of your package.
But what about type declarations?
So let's presume we have myStruct
in Package1
, and then a struct within Package2
wants an instance of myStruct
passed in via a type declaration on a method signature:
package Package2
type SomeService struct { }
func (*SomeService) DoSomething(obj myStruct) { /* WONT WORK!!*/ }
Oops! Because myStruct
is from Package1
and is not an exported type any more, Package2
can't access it. We can't even ask for an interface{}
and type assert because the type is not exported; it simply doesn't exist in this context.
Interfaces
One way to maintain a private struct but be able to typehint for it in another package would be via a public interface for MyStruct
. You cannot initialise an interface, but we can use the type in other packages:
package Package1
/* The interface is exported, so publically available */
type MyStructsInterface interface {
SomeInterfaceMethod() int
}
/* Private struct */
type myStruct struct {
num int
}
func NewMyStruct() myStruct {
return MyStruct{num: 22}
}
/* With this, myStruct now implicitly implements MyStructsInterface */
func (*myStruct) SomeInterfaceMethod() int { return 42 }
Now in other packages' function declarations we can typehint for an instance of MyStructsInterface
and use it's public API, and we're also only able to construct the object in a valid state with the NewMyStruct
function. GREAT!
So this just means we need to...
Interface Everything
This means that we now have to fully interface everything that we want to be able to use in another package, if we want to protect from the object being built in a way we do not intend. In some cases this is fine, however interfacing everything is overkill and absolutely unnecessary. What about objects that don't actually have any public methods, just a bunch of properties? We can't interface those with because of Go's implicit interfaces (there's no method to add to an interface to say "our object implements this interface now") and this would be a code smell anyway.
Nil Pointers
Let's say we decide that interfacing everything is a stupid idea - it is not pragmatic nor necessary and we would be doing it because of the lack of a language feature, not because we actually need them, which points to this being a code smell. Instead, let's publically expose that struct so we can typehint for it and see what can go wrong.
type MyStruct struct {
child *ChildStruct
}
func (s MyStruct) StructFunc() {
/* Here's where things go wrong */
fmt.Println(s.child.num)
}
type ChildStruct struct {
num int
}
func DoSomething(obj MyStruct) {
obj.StructFunc()
}
DoSomething(MyStruct{})
We can pass in a MyStruct
initialised only with MyStruct{}
. The function it is passed into calls s.child
, which will be nil
, and when we try and call .num
on this nil we will get:
panic: runtime error: invalid memory address or nil pointer dereference
So we would have to guard against this:
type MyStruct struct {
child *ChildStruct
}
func (s *MyStruct) StructFunc() {
if s.child != nil {
fmt.Println(s.child.num)
}
}
Above we declare a function on *MyStruct
. What about if s.child
isn't a pointer but instead is a value (struct)?
Empty Structs
Consider the following:
type MyStruct struct {
/* Note how s.child is not a pointer any more */
child ChildStruct
}
func (s *MyStruct) StructFunc() {
if s.child != nil {
fmt.Println(s.child.num)
}
}
The != nil
check will no longer work. We will get the following panic:
prog.go:22:16: cannot convert nil to type ChildStruct
So instead we have to check for an empty struct:
func (s MyStruct) StructFunc() {
if s.child == (ChildStruct{}) {
return
}
fmt.Println(s.child.num)
}
This is just another way through which we have to defend code against nils if we allow the type to be exported.
Where we are now
We have the following options:
- Check for a
nil
for every pointer we have a type declaration for, in every function in the codebase - Check for an empty struct for every concrete type we have a type declaration for, in every function in the codebase
- Interface everything so we can only build objects in a valid state, and then there is no easy way to create an invalid
MyStruct
- we can't usepackage1.myStruct{}
and we can't usenew(package1.myStruct)
- onlypackage1.NewMyStruct()
.
But I don't want to interface everything!!
Encapsulation
A language mechanism for restricting direct access to some of the object's components.
I require the ability to restrict any object's initialisation to a single entrypoint which guarantees that it cannot be initialised in an invalid state and rightly so, in whatever programming language I am using that has some object oriented capabilites. But in doing so, in Golang, I require an interface to typehint for it anywhere else because of package visibility and a lack of real constructors.
In PHP for example, we have: __construct()
which can be the only way to create an instance of a class, and then via that, and our implementation, we guarantee the object exists in a valid state and we can typehint for that as a dependency elsewhere ensuring we only get that valid object to work on. It doesn't seem like I can do anything this with Golang. Is there anything better?
Implicit Interfaces
Go is special in the way that it's not like Java, PHP or other OO languages with explicit interfaces. For example in PHP, our object would implement an interface only when we explicitly type that it does:
interface MyInterface { }
class MyClass implements MyInterface { } // Explicit
In Go on the other hand, we have implicit interfaces:
type MyInterface interface {
MethodMustBeImplemented()
}
type MyStruct struct { }
func (*MyStruct) MethodMustBeImplemented() { } // Implicit
Idiomatic Go suggests that sometimes you should define interfaces not next to or near the class that implements them, as you would do with traditional OO languages, but rather alongside the type that will use it. This is an immensely powerful feature once you get to grips with what it means for your code.
Imagine the following struct again with our New
best practice used. Note that we are not exporting the type, so we can avoid the problems mentioned earlier:
package Package1
type myStruct struct { }
func (*myStruct) SomeFunc() int { return 42 }
func (*myStruct) SomeOtherFunc() string { return "42" }
/* Factory */
func NewMyStruct() *myStruct {
return &myStruct{}
}
Now a struct in Package2
requires the ability to use this struct - but let's think about this for a moment. What exactly does Package2 want? - The answer is not the struct, but the ability to call an API method on the struct.
We could go about declaring a globally available interface, which I've already elaborated wasn't the best way of doing things. We don't want to interface everything.
But let's say that our consumer actually only wants to call one method on this struct. We can define an implicit interface, next to the consumer, only containing the one method!
package Package2
/* Implicit interface time! */
type interfaceToConsume interface {
SomeFunc() int
}
Immediately on creating this interface in Package2
, the struct from Package1
can actually fulfil this requirement and fulfil this interface. Now if we add the following code to Package2
, we can take in an instance of MyStruct
without explicitly declaring that we need to do so with the struct that is not publically available.
type consumer struct { }
func NewConsumer() *consumer {
return &consumer{}
}
func (*consumer) DoSomething(obj interfaceToConsume) {
/* We can actually pass in a Package1.myStruct here! */
}
How can we pass in a Package1.myStruct
when it is private?
package Package3
myStruct := Package1.NewMyStruct()
consumer := Package2.NewConsumer()
consumer.DoSomething(myStruct)
From this we have gained the following:
- Types are private so they cannot be initialised in an invalid state
- We utilise Go's equivalent of constructors: factory functions for initialisation
- Idiomatic Go usage in defining the interface next to the consumer
- The implicit interface defined next to the consumer is also Private, which means we're not going to pollute the global namespace with random interfaces everywhere
- Smaller interfaces for simpler test mocks - we don't need every method, only the ones we are implementing
- Also adhereing more towards the Interface Segregation Principle of SOLID (smaller, more specific interfaces AKA role interfaces)
- You can remove all of the nil and empty struct checks that you repeat throughout the application because you have guaranteed that you can no longer have any of your objects in an invalid state
- In the case that you do want to define a public interface to use everywhere, that's still perfectly fine as well, whether or not it is next to the consumer or traditionally next to the implementation
Understanding this required a bit of a mindset change for me but it helped remove a lot of boilerplate and trust your code more. But there are always tradeoffs. Here however, I think there a less chances of error.
Conclusion
But you said you don't like having to declare an interface for every object! Well, we're not creating one huge interface for each struct and that technicality makes the difference. We are defining specific subsets of functionality that other structs can fulfil next to the consumer!
Usually there are tradeoffs to make in any language. For me, this is just a more fundamental one that I have to consider when designing the code. I would very much like to be able to request a valid type and not have to check that it has been initialised correctly, as there should only be a specific method of initialisation, and I would like to not have to create one huge interface for every object in the application.
It turns out that Go's implicit interfaces can provide the safety needed.
I'm really enjoying learning Go and having my mindset challenged and changed as I learn more. Golang definitely has an interesting way of doing things.
Is this also how you write defensively in Go, or have you found a better way of doing things? Let me know what you think in the comments.