Why Go has no constructors
In many languages a constructor is a special member of a class that runs when you create an object. Go has no such feature. A struct value can be created anywhere with a literal, and Go does not run any code when it happens. The convention that fills the gap is a plain function, usually named New followed by the type name, that builds the value, sets up its fields and returns it.
Setting fields by hand after creating the value works, but it is verbose and easy to forget:
var dog Dog
dog.name = "Rex"
dog.age = 4
A constructor function does all of it in one call.
Writing a constructor function
A constructor function is an ordinary function with two habits:
- Its name starts with
New, then the type name:NewDogforDog. The prefix is only a convention, and Go treats the function like any other. - It returns the finished value.
type Dog struct {
name string
age int
}
func NewDog(name string, age int) Dog {
return Dog{name: name, age: age}
}
Inside, Dog{name: name, age: age} is a struct literal with named fields. In each pair the part before the colon is the field and the part after it is the parameter, so name: name is not a typo. Because the fields are named, the order does not matter, and any field you leave out gets its zero value.
Calling it
rex := NewDog("Rex", 4)
luna := NewDog("Luna", 7)
When these lines finish, rex.name is "Rex" and rex.age is 4, and luna holds its own values. Each call builds a separate value.
Value or pointer, and validation
This lesson's NewDog returns a Dog value. Many Go constructors return a pointer, *Dog, by writing return &Dog{name: name, age: age}. Returning a pointer avoids copying a large struct and lets all callers share one object. For a small struct a value is fine. Either way the calling code looks the same, and rex.name works on both.
A constructor is also the right place for checks, because it is the one function every new object passes through. A constructor that can fail commonly returns an error as a second result:
func NewDogChecked(name string, age int) (Dog, error) {
if age < 0 {
return Dog{}, fmt.Errorf("age %d is negative", age)
}
return Dog{name: name, age: age}, nil
}
The exercise does not need this, but it shows what a plain function allows that a built-in constructor would not.
The lesson's exercise
The Dog struct already has name and age. The task has two parts.
- Declare
NewDogbelow the struct, with parametersname stringandage int, returning aDogbuilt from a literal that sets both fields. - Create the dogs in
mainFunctionwith:=, callingNewDog("Rex", 4)andNewDog("Luna", 7). The code below them prints the fields.
type Dog struct {
name string
age int
}
func NewDog(name string, age int) Dog {
return Dog{name: name, age: age}
}
func mainFunction() {
rex := NewDog("Rex", 4)
luna := NewDog("Luna", 7)
fmt.Printf("%s is %d\n", rex.name, rex.age)
fmt.Printf("%s is %d\n", luna.name, luna.age)
}
It prints:
Rex is 4
Luna is 7
In Printf, %s formats a string and %d an integer, and the \n ends the line, since Printf adds no newline itself.
Common mistakes
- Swapping the arguments.
NewDog(4, "Rex")does not compile, because the types of the parameters differ. When two parameters share a type, the compiler cannot catch a swap, so check the order. - Expecting an automatic call. Writing
var d Dognever callsNewDog. The constructor runs only when you call it, so a zero-valueDogcan exist with an empty name. - Omitting the field names. The unkeyed form
Dog{name, age}compiles, but it depends on the order of the fields in the struct and breaks as soon as a field is added or moved. Prefer the keyed form. - Naming the constructor in lower case.
newDogworks inside the package but is hidden from other packages. UseNewDogwhen other packages should call it.
Key takeaways
- Go has no constructors; a
NewXxxfunction is the convention. - A struct literal with named fields builds the value.
- The function can return a value or a pointer, and can return an error if arguments are invalid.
- Nothing forces callers to use the constructor, so design the zero value to be usable too.