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:
- may take multiple arguments (equivalent to PHP, Java, etc.),
- 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 declareNewPerson
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 equivalent from PHP would be something like
-
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.
- This goes against the wisdom of imperative or procedural programming. You would asume that
-
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 aCrontab{}.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
andBackground
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:
- Go with Databases
- Advent of Go Microservices
- API Foundations in Go
- 12 Factor Apps with Docker and Go
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.
Want to stay up to date with new posts?
Stay up to date with new posts about Docker, Go, JavaScript and my thoughts on Technology. I post about twice per month, and notify you when I post. You can also follow me on my Twitter if you prefer.