Skip to main content
eLearner.app
Module 5 · Lesson 5 of 525/50 in the course~10 min
Module lessons (5/5)

The `New...` constructor pattern

Go has no constructor keyword. The convention is a factory function that returns a properly initialized value or pointer. By name it is typically called NewT (or NewXxx).

Base pattern: NewT

Go
type User struct {
    Name string
    Age  int
}

func NewUser(name string, age int) *User {
    return &User{Name: name, Age: age}
}

u := NewUser("Ada", 36)
fmt.Println(u.Name)

Benefits over the direct literal &User{...}:

  • You centralize validation and defaults.
  • You can have unexported fields and populate them only from the constructor.
  • You can change the implementation without breaking callers.

Constructor with validation: (*T, error)

When initialization can fail, return (*T, error):

Go
import "errors"

func NewUser(name string, age int) (*User, error) {
    if name == "" {
        return nil, errors.New("nome obbligatorio")
    }
    if age < 0 {
        return nil, errors.New("eta non valida")
    }
    return &User{Name: name, Age: age}, nil
}

u, err := NewUser("Ada", 36)
if err != nil {
    // handle...
}

This is the idiomatic pattern for any constructor with preconditions: db connection, file handle, server config, etc.

Multiple constructors

If you have multiple "ways" to construct, use descriptive names:

Go
func NewUser(name string) *User { ... }
func NewUserFromJSON(data []byte) (*User, error) { ... }
func NewAdmin() *User { ... }

No overloading in Go: the name disambiguates.

Functional options (advanced glimpse)

When there are many optional parameters, the idiomatic pattern is "functional options":

Go
type Option func(*Server)

func WithPort(p int) Option {
    return func(s *Server) { s.Port = p }
}

func WithTLS(cert, key string) Option {
    return func(s *Server) { s.Cert, s.Key = cert, key }
}

func NewServer(opts ...Option) *Server {
    s := &Server{Port: 8080} // default
    for _, opt := range opts {
        opt(s)
    }
    return s
}

s := NewServer(WithPort(9000), WithTLS("c.pem", "k.pem"))

It is more verbose than a literal but scales well with many optional parameters and preserves backward compatibility (adding a new Option does not break existing callers).

When you do NOT need a constructor

If the type's zero value is already useful, let the caller use the direct literal. Canonical example: sync.Mutex has no constructor, var m sync.Mutex is already a ready-to-use mutex. Same for bytes.Buffer.

The rule is: "Make the zero value useful". If you can, no mandatory constructor.

Try it

Exercise#go.m5.l5.e1
Attempts: 0Loading…

Write the factory function NewUser(name string, age int) *User that returns &User{Name: name, Age: age}.

Loading editor…
Show hint

Use the literal `&User{...}` to return a pointer.

Solution available after 3 attempts

Exercise#go.m5.l5.e2
Attempts: 0Loading…

Turn NewUser into (name string) (*User, error): if name == '' return nil, errors.New('nome obbligatorio').

Loading editor…
Show hint

If there are preconditions, return (*T, error) and return nil + error on failure.

Solution available after 3 attempts

Quiz#go.m5.l5.e3
Ready

What is the idiomatic convention for constructors in Go?

Go
// Which signature is more idiomatic?
Answer options

Recap

  • No constructor: use a NewT function (or New inside the same-named package).
  • Return *T for the simple case; (*T, error) with preconditions or validation.
  • On error: return nil as the pointer and the explanatory error as the second value.
  • No overloading; use descriptive names (NewUserFromJSON, NewAdmin).
  • "Make the zero value useful": if you can, the constructor is unnecessary.
  • Advanced pattern for many optional parameters: functional options.