An argument for value receiver constructors

When it comes to object-oriented programming, there’s so much prior work done before Go, that a lot of newcomers to Go can’t help but to bring some concepts over. One of these concepts are object constructors, for which Go doesn’t have an equivalent.

Why constructors?

There are objects in Go that have to be initialized, for example channels and slices immediately come to mind. You’ll have to call make to set that up.

“The make built-in function allocates and initializes an object of type slice, map, or chan (only). Like new, the first argument is a type, not a value. Unlike new, make’s return type is the same as the type of its argument, not a pointer to it.”

And obviously, there are default values to consider, so allocation itself isn’t the only reason why we need constructor functions in Go (or any other language, really). As the definition for the struct’s don’t allow for specifying default values, you’ll need to define a function, which will set the default values for you.

Constructors in the wild

The generally accepted paradigms for providing constructors is to provide a function New or NewStructName where StructName is the returning value of the constructor. Generally, New is used in small, self contained packages that care to expose only a single struct, for example, fatih/structs. If you care to construct several objects from one package, then you can look no further than the stdlib time package, for time.NewTicker() and time.NewTimer().

But the time package exports a number of other structs, that don’t have such constructors?

Structs that don’t need an allocator or default value setup can completely omit the constructor, so providing one is useful only to conform to a coding standard, or to future-proof the API somewhat, in case it becomes necessary at a later time. Of course, a lot of common constructors just don’t follow these guidelines, for example time.Now provides default values, but it’s naming is descriptive so that when you read the code, you know that you expect a value with the current timestamp, as opposed to the Unix epoch or something.

Value receivers

This is how I propose that a value receiver constructor would look like:

type Person struct {
    name string
}

func (Person) New(name string) *Person {
    return &Person{name}
}

And to use such a constructor, you would invoke Person{}.New("Tit Petric") and end up with the initialized object. We can in fact reason better about the code we are writing, because we can asume that we’ll end up with a Person object (or a pointer to it), because that’s what we start with.

Fact check

So, I’m telling you that there’s no performance or memory impact from using value receivers over usual function constructors. Are you going to believe me? Well, let me ease your mind with a benchmark.

New         2000000     754 ns/op     0 B/op      0 allocs/op
Person.New  2000000     786 ns/op     0 B/op      0 allocs/op

And there’s the code for the benchmark, if you want to run it yourself (copy it into your own main.go and issue a go run main.go - it will not work on playground due to time limits).

Still not enough proof? Let’s take this smaller example and dump the assembly code. Save it as main2.go and run go tool compile -S main2.go > main.s. Review the new main.s files for the assembly code. Our constructors are invoked on line 21 and 25 respectfully:

(main2.go:21)      MOVQ    AX, (SP)
(main2.go:21)      PCDATA  $0, $0
(main2.go:21)      CALL    runtime.newobject(SB)
(main2.go:21)      MOVQ    8(SP), AX
(main2.go:21)      MOVQ    $10, 8(AX)
(main2.go:21)      MOVL    runtime.writeBarrier(SB), CX
(main2.go:21)      TESTL   CX, CX
(main2.go:21)      JNE     312
(main2.go:21)      LEAQ    go.string."Tit Petric"(SB), CX
(main2.go:21)      MOVQ    CX, (AX)

and

(main2.go:25)      MOVQ    AX, (SP)
(main2.go:25)      PCDATA  $0, $0
(main2.go:25)      CALL    runtime.newobject(SB)
(main2.go:25)      MOVQ    8(SP), AX
(main2.go:25)      MOVQ    $10, 8(AX)
(main2.go:25)      MOVL    runtime.writeBarrier(SB), CX
(main2.go:25)      TESTL   CX, CX
(main2.go:25)      JNE     279
(main2.go:25)      LEAQ    go.string."Tit Petric"(SB), CX
(main2.go:25)      MOVQ    CX, (AX)

The constructor code is completely the same, and in both cases, the functions get fully inlined, so whichever format you prefer to write results in completely the same assembly code output.

For those of you not familiar with “inlining”:

In computer science, an inline function is a programming language construct used to tell a compiler it should perform in-line expansion on a particular function. In other words, the compiler will insert the complete body of the function in every place in the code where that function is used.

In our case this means that the assembly code only performs the one allocation inside the constructor. The value receiver doesn’t declare a variable to use the receiving value, and the compiler fully optimizes this away.

Conclusion

While value receiver constructors do feel slightly awkward, it’s as close to analogous copy of constructors from other languages. While in PHP the constructor would be invoked when you call new Person(...) and would only return objects of that type, Go actually allows you to go beyond that with Person{}.New().

Constructors like these:

  1. may take multiple arguments (equivalent to PHP, Java, etc.),
  2. may return multiple values, allowing for Go-style error handling

There are a lot of packages that already provide New, even in the stdlib. I obviously didn’t research all of them, but errors comes to mind. As for an example of the second form, the Google Youtube APIs provide New(*http.Client) (*Service, error) for your usage. There are many of these examples littered around.

When writing your own applications however, the accepted wisdom is that you shouldn’t create subpackages unless you have clear reasons for doing so. Even more dramatic would be creating individual packages for each one of your structs, so you may provide separate New constructors for them. It’s good to have a second opinion in this case.

I realize there are other considerations as well:

  • The value receiver is bound to an instance of Person, and not the global scope where you would declare NewPerson if you’d have chosen that form of constructor,

    • the equivalent from PHP would be something like Person::New() and not the actual __constructor function that PHP has. No globals involved.
  • The Go compiler fully optimizes away the implied overhead / allocation from the Value receiver. In fact, there’s absolutely no difference in the produced assembly code from either form as you saw above.

    • This goes against the wisdom of imperative or procedural programming. You would asume that Person{} would imply an allocation, and then executing the value receiver would throw that allocation away to be garbage collected. The Go compiler is smarter than that, in fact it’s super smart and fully optimizes this away as seen above. Compilers can be wicked smart.
  • You may use both the value receiver constructor and a constructor func where it makes sense. This is an example where a package provides both New() and a Crontab{}.New() constructor, while other structs just provide the value reciever constructors.

  • Regardless of the number of structs you declare, you can have a New constructor for each of them. Obviously you don’t need to have just one constructor, if there are many ways you’re constructing your objects. For example, the context package provides four of them.*

    * The TODO and Background returns don’t return new values on each call.

While I have you here...

It would be great if you buy one of my books:

I promise you'll learn a lot more if you buy one. Buying a copy supports me writing more about similar topics. Say thank you and buy my books.

Feel free to send me an email if you want to book my time for consultancy/freelance services. I'm great at APIs, Go, Docker, VueJS and scaling services, among many other things.