Advanced go - Choosing between a constructor and declaring the type

In go there are two ways to create structures.

Either you instantiate the structure directly, populate its fields. Or you use a constructor, that will populate the structure fields.

Suppose we have a structure:

type Foo struct {
    Fuz int
    Bar []string

You can either instantiate the structure field by field:

f := Foo{Fuz: 3, Bar: []string{"abc", "123"}}

Or you can use a constructor:

func NewFoo(fuz int, bar []string) *Foo {
    return &Foo{Fuz: fuz, Bar: bar}

Which method to prefer is not always clear, and sometimes is not simple.

This is a problem in both designing the structure, and in using it.

Usually the designer of the struct provides, implicitly or explicitly, a suggested way to instantiate the structure.

If there is a constructor, the user of the structure feel encouraged to use the constructor, if the constructor is not present, or if it is hide, the user will be forced to instantiate the structure field by field.

Pros and Cons

Instantiating a structure field by field makes more explicit how the different values are used and how variables mix and match with each other. There is no magic and everything is explicit, in front of your eyes. However, nothing prevents you to not set a value, or set it to a nil.

Using a constructor force the user to provide all the values necessary. Otherwise, the function does compile. Nothing force the user of the constructor to not provide a zero value for some fields, but it is possible to validate the arguments to the constructor in the constructor itself and return a sensible error if something goes wrong. Similarly, it is possible to check if the argument provided make sense. Moreover, the use of the constructor allows providing a more ergonomic interface and manipulate internal arguments in a way that otherwise would be impossible.

Does the zero value make sense?

When designing a new structure, I always wonder if I should provide a constructor or not.

If the zero value of the structure makes sense, and can be safely used, this is a good indication to not provide a constructor.

The user can just instantiate the structure, personalize it which whatever argument is needed, and everything will be explicit in front of the developer.

However, if the zero value of the structure does not make sense, or it could be misused, then, it is a good indication to provide a constructor.

This implies that the constructor should, almost, always return the structure and an error. If the constructor returns only the structure, and it cannot fail, it is a good indication that the constructor is not needed. The main exception to this rule is a constructor for ergonomic reasons.