Constructors

A struct literal is fine when every field is something the caller has ready to hand. Plenty of real types are not like that: a timeout needs a sensible default, a socket needs to be opened, a hash needs to be seeded. For those, Go uses a constructor function, named NewX by convention, that builds the struct and returns a pointer to it.

Why not just a literal?

Consider an HTTP client. A caller wanting "the usual thing" should not have to fill in every field:

type Client struct {
    URL     string
    Token   string
    Timeout time.Duration
    Retries int
}

// Painful: every caller repeats the defaults.
c := Client{
    URL:     "https://api.example.com",
    Token:   "secret",
    Timeout: 30 * time.Second,
    Retries: 3,
}

Two callers have to agree on what "30 seconds" and "3 retries" mean. If the defaults change, every call site changes. A constructor centralises the defaults in one place:

func NewClient(url, token string) *Client {
    return &Client{
        URL:     url,
        Token:   token,
        Timeout: 30 * time.Second,
        Retries: 3,
    }
}

// Callers supply what only they know; the type owns its own defaults.
c := NewClient("https://api.example.com", "secret")

The caller provides the required arguments; the constructor fills in the defaults. Two callers get the same behaviour without having to know what the defaults are.

The NewX convention

The naming rule is simple: a constructor for type T is called NewT, lives in the same package as T, and returns *T.

func NewClient(url, token string) *Client { ... }
func NewUser(name, email string) *User    { ... }
func NewServer(addr string) *Server       { ... }

The standard library follows this everywhere. bytes.NewBuffer, strings.NewReader, bufio.NewScanner, http.NewRequest, log.New. If you have used any Go stdlib type with internal state, you have already called a constructor named this way.

When the package itself is named after the type (so package client's only type is Client), the convention is New with no suffix:

// In package client:
func New(url, token string) *Client { ... }

// At a call site in another package:
c := client.New(url, token)

client.New(...) reads better than client.NewClient(...) once the import path already carries the type name.

Why *T, not T

Constructors return *T almost always, for the same reasons you take *T parameters for domain types:

  • The caller usually wants to share one canonical instance across the rest of the program, not a value that silently copies on every assignment.
  • Methods on the type (coming next chapter) commonly need to mutate fields, and a method with a pointer receiver cannot be called on an unaddressable value.
  • Returning *T makes the type's "identity" explicit. Two pointers to the same Client are the same client.

The exception is small, arithmetic-flavoured types (Point, Duration, Money) whose constructors, if they have one, return the value directly. For everything else, *T is the default.

Unexported struct, exported constructor

A common pattern: the struct type itself is unexported (lowercase), and the constructor is the only way to build one from outside the package.

// package counter

type counter struct {   // unexported type, lowercase
    name  string
    value int
}

func New(name string) *counter {
    return &counter{name: name, value: 0}
}

func (c *counter) Inc()           { c.value++ }
func (c *counter) Value() int     { return c.value }

Outside the package, callers cannot write counter{} directly (the type name is unreachable), so they have no choice but to go through New. That gives the package a single construction path to enforce invariants: a minimum retry count, a mandatory timeout, a parsed URL. No zero-valued counter escapes the package.

Real Go codebases do this a lot, especially for types that need initialisation beyond a few assignments.

Constructors that can fail: (*T, error)

When construction needs to do something that can go wrong (parse a URL, open a connection, validate arguments), the constructor returns (*T, error):

func NewClient(url, token string) (*Client, error) {
    if url == "" {
        return nil, errors.New("url is required")
    }
    return &Client{
        URL:     url,
        Token:   token,
        Timeout: 30 * time.Second,
    }, nil
}

The caller uses the short-statement if err != nil you saw in Chapter 2:

c, err := NewClient("https://example.com", "secret")
if err != nil {
    // handle err, and do not use c
}
// safe to use c

Errors get their own chapter later in the course. For now, the shape to remember is: if construction can fail, add error as a second return.

Reach for NewX the moment the zero value stops being useful

If var x T is immediately usable (like var buf bytes.Buffer), no constructor is needed; the zero value is the constructor. If a newly-declared value of your type is unusable until some fields are set in a particular way, write a NewX that returns *T with those fields already set. That draws a clean line between "here is a value ready to work" and "here is an uninitialised value that will crash if touched".

Where optional parameters go

NewClient(url, token) works for types with a few required arguments. Types with many optional settings (timeout, retries, user-agent, TLS config) grow an awkward signature fast. The idiomatic Go solution for that case is the functional options pattern (NewClient(url, WithTimeout(30), WithRetries(3))), covered in the Idioms chapter later in the course. For simple types, plain positional arguments are enough.

Task

The starter declares:

type Counter struct {
    Name  string
    Value int
    Step  int
}

Write a constructor NewCounter(name string) *Counter that returns a pointer to a Counter with:

  • Name set to the name argument
  • Value set to 0
  • Step set to 1 (the sensible default for "increment by one")

In main, the starter already calls NewCounter("hits") and prints each field on its own line. With your implementation, the expected output is:

hits
0
1
Expected output
hits
0
1