How to write idiomatic constructor

Issue

I’m confused about the constructors in Go. Most constructors I’ve seen return a struct, but ‘Effective Go’ suggests that an interface can be returned in some cases, according to the rule of ‘Generality’.

I trust ‘Effective Go’ to provide good ideas, but this doesn’t seem to follow the principle of ‘accept interfaces, return structs’. I guess that many types implement an interface and nothing more than that, so in that case it would be common to see constructors which return interfaces.

Another related statement is that interfaces should be defined by the consumer, but ‘Generality’ means that the interface is defined by the producer.

Can someone clarify?

Solution

As it has already been mentioned, returning an interface should be considered something exceptional.

Returning errors of type error which is an interface is one of those exception.

Returning an interface that represents an unexported type is the other exception. But why would you have an exported interface that describes an unexported struct instead of just having an exported struct?

The reason is simple, that allows you a higher degree of control on how that struct is constructed.

Compare this two pieces of code:

type MyType struct {
    MyField string
}

func NewMyType(value string) MyType {
    return MyType{value}
}

func (t MyType) MyMethod() string {
    return t.MyField
}
type MyType interface {
    MyMethod() string
}

type myType struct {
    MyField string
}

func NewMyType(value string) MyType {
    return myType{value}
}

func (t myType) MyMethod() string {
    return t.MyField
}

In the first case I would be able to do: myVar := MyType{} while in the second case I won’t be able to do so, I am forced to use the provided constructor. The first case also allows to modify the field value after creation which is not allowed in the second case. Making the field unexported will solve the second part but not the first.

This example is obviously trivial, but being able to construct invalid structs may have a horrible impact. By having specific constructors you can ensure that the object is in a valid starting state and you will only need to make sure it always stays in a valid state. If you can’t ensure that, you may need to check that it is in a valid state at the start of every method.

For example consider a DB request. It needs a DB connection. If the user is able to create a DB request without a DB connection you will have to check that it is valid in every method. If you enforce him to use a constructor you can check at creation time and done.

Answered By – Adirio

Answer Checked By – Senaida (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.