The reason I have this question is that I often make mistakes that I forgot to specify a value to a struct's field, then the compiler is fine, but the zero value causes bugs.
Here is an example. Say I have a type Foo
defined like this in a package:
package types
type Foo struct {
RequiredField1 string
RequiredField2 string
}
It is exposed and has 2 fields that I'd like both of them to be specified when the Foo
struct is created.
Then I can import and use it in my main function like this:
package main
import (
"github.com/foo/bar/types"
)
func main() {
f := &Foo{
RequiredField1: "fff",
}
fmt.Printf("f.RequiredField2: %v", f.RequiredField2)
}
This causes a bug because I forgot to specify RequiredField2
which is required. And I often make this kind of bugs :p
Now, in order to leverage the compile and prevent this mistake, I made a constructor function for Foo
and asks for the required values.
func NewFoo(field1 string, field2 string) *Foo {
return &Foo{Field1: field1, Field2: field2}
}
So that if I forgot to pass field2
, the compiler won't compile my code:
func main() {
f := NewFoo("foo")
fmt.Printf("f.Field2: %v", f.Field2)
}
The build will fail:
./prog.go:18:13: not enough arguments in call to NewFoo
have (string)
want (string, string)
Now the question is: how to stop the foreign callers (callers from other namespaces) from calling the default constructor of Foo
, that is &Foo
, and force them to use the NewFoo
?
If I can, then I'm safe since NewFoo is the only way to create the Foo
type and the compiler helps me ensure all fields are present when calling it.
You can avoid this problem by making Foo type unexported.
You can make an interface that is Exported and has all the methods that Foo type performs.
Example:-
type Foo interface{
DoSomething()
}
type foo struct{
RequiredField1 string
RequiredField2 string
}
func NewFoo(requiredField1 string,requiredField2 string)*foo{
return &foo{
RequiredField1:requiredField1,
RequiredField2:requiredField2,
}
}
func (f *foo)DoSomething(){
// your implementation
}
In this way all the caller will be able to access it but cannot create foo without NewFoo function.
How to hide the default type constructor in golang?
You cannot for an exported type.
Get used to it, it is not a problem in real life.
(For unexported types, just provide a func NewUnexportedType(...) unexportedType
. )
it's helpful to arrange when designing your data structures that the zero value of each type can be used without further initialization.
we often need to change the type definition, like adding more fields
That is why you should make the zero value for struct fields significant. The behavior is by design. For example, it is used to maintain the Go 1 compatibility guarantee.