Question

Nil Detection for Structs and Pointers in Go

gonull

Question

In Go, error values are often checked for nil:

if err != nil {
    // Handle the error.
}

However, given this struct and a Config value:

type Config struct {
    host string
    port float64
}

var config Config

why does this comparison produce the compiler error cannot convert nil to type Config?

if config == nil {
    // ...
}

How should you determine whether a configuration value is present or has been initialized?

Short Answer

You will learn why a Go struct value cannot be nil, which Go types can be nil, and how to represent an optional Config using a pointer or an explicit validity rule.

Concept

nil means “no value” for specific Go types. It is not a universal empty-value marker.

A Config variable declared as a struct value always contains a Config value, even when you do not assign one yourself:

var config Config

This creates the struct's zero value:

Config{host: "", port: 0}

Because config is an actual struct value, it cannot be compared with nil. That is why Go reports an error.

In contrast, err is commonly an error interface value. Interfaces can be nil, so this is valid:

if err != nil {
    // An error value is present.
}

If a configuration itself may be absent, represent it with a pointer:

var config *Config

if config == nil {
    // No Config has been assigned.
}

This distinction matters because Go makes absence explicit. A value-type struct represents data that always exists; a pointer can represent either an address to data or no data at all.

Mental Model

Think of a struct value as a form that always exists on your desk. A new form may have blank fields, but the form is still there.

var config Config

config is that blank form. It is not nil.

A pointer is like a note containing the location of a form. The note can either contain a location or be empty:

var config *Config

When the note is empty, its value is nil. Before reading fields through the pointer, check whether the note contains a location.

Take Quiz

Syntax and Examples

A struct value cannot be nil:

type Config struct {
    host string
    port float64
}

var config Config

// Invalid: Config is a struct value, not a nil-able type.
// if config == nil { }

Use a pointer when absence is meaningful:

type Config struct {
    host string
    port float64
}

var config *Config

if config == nil {
    fmt.Println("No configuration was supplied")
}

config = &Config{
    host: "localhost",
    port: 8080,
}

if config != nil {
    fmt.Println(config.host)
}

The &Config{...} expression creates a Config and produces a pointer to it. Since config has type *Config, comparing it with nil is valid.

If the struct must always be present, check its fields according to your application's definition of “valid”:

config := Config{host: "localhost", port: 8080}

if config.host == "" || config.port <= 0 {
    fmt.Println("Configuration is incomplete")
}

There is no universal “empty struct” check. Your program must define which field values are acceptable.

Step by Step Execution

Consider this program:

package main

import "fmt"

type Config struct {
    host string
    port float64
}

func main() {
    var config *Config

    fmt.Println(config == nil)

    config = &Config{host: "api.example.com", port: 443}

    fmt.Println(config == nil)
    fmt.Println(config.host)
}

Execution trace:

  1. var config *Config declares a pointer variable.
  2. A pointer with no assigned address gets the zero value nil.
  3. config == nil is true, so the first line prints true.
  4. &Config{...} creates a Config value and assigns its address to config.
  5. config == nil is now false because the pointer refers to a Config.
  6. config.host follows the pointer and reads the host field, printing api.example.com.

By comparison, this declaration creates the value directly:

var valueConfig Config

valueConfig is immediately usable and has the zero-value fields "" and 0, but it cannot be nil.

Real World Use Cases

  • Optional dependency: A service may accept an optional cache configuration. Use *Config when no cache configuration is a valid state.
  • JSON and API requests: A pointer field can distinguish a missing nested object from a supplied object with zero-valued fields.
  • Database records: A pointer is useful when a related record may not exist, such as an optional user profile.
  • Configuration loading: A loader can return *Config and an error. A non-nil error indicates failure; a non-nil config indicates successful loading.
  • Validation: A required Config value can be passed by value, then validated by checking required fields such as host and port.
Take Quiz

Real Codebase Usage

In production Go code, choose the type based on the meaning of absence.

Required configuration: use a value

func NewServer(config Config) error {
    if config.host == "" {
        return fmt.Errorf("host is required")
    }
    if config.port <= 0 {
        return fmt.Errorf("port must be positive")
    }

    return nil
}

This is a guard-clause style: invalid input returns early. The caller must provide a Config, and the function validates its contents.

Optional configuration: use a pointer

func NewClient(config *Config) error {
    if config == nil {
        return fmt.Errorf("config is required")
    }
    if config.host == "" {
        return fmt.Errorf("host is required")
    }

    return nil
}

Checking config == nil must happen before accessing config.host; otherwise the program can panic by dereferencing a nil pointer.

Defaults without pointers

When zero values have a useful meaning, a value struct is often simpler:

func withDefaults(config Config) Config {
    if config.port == 0 {
        config.port = 8080
    }
    return config
}

This pattern treats port 0 as “use the default port,” rather than treating the entire config as absent.

Common Mistakes

Comparing a struct value to nil

var config Config
if config == nil { // Does not compile.
}

A struct is not a nil-able type. Use *Config if the whole object may be absent, or validate individual fields if it is always present.

Dereferencing a nil pointer

var config *Config
fmt.Println(config.host) // Panic at runtime.

Check first:

if config != nil {
    fmt.Println(config.host)
}

Treating zero values as automatically invalid

if config.port == 0 {
    // This is not always an error.
}

Zero is the default numeric value. It might mean “not configured,” “choose a default,” or be valid in another domain. Define and document the rule for your application.

Comparing a struct to an empty struct when fields are not comparable

For this simple struct, the following is valid because string and float64 are comparable:

if config == (Config{}) {
    // All fields have their zero values.
}

However, this does not work if the struct contains a slice, map, or function field, because those fields cannot be compared except to nil:

type BadComparison struct {
    tags []string
}

// BadComparison{} == BadComparison{} // Does not compile.

Prefer explicit validation methods when checking configuration quality.

Comparisons

Type or approachCan be nil?Best use
ConfigNoConfiguration always exists; use zero values or validation rules.
*ConfigYesThe entire configuration may be absent.
errorYesA function may have no error or an error value.
map[string]stringYesAn optional map; reads from a nil map are safe, writes are not.
[]stringYesAn optional or empty sequence; nil and empty slices often behave similarly when ranging over them.

Config{} versus nil

Config{}      // A real Config with zero-valued fields.
(*Config)(nil) // No Config is present.

Use Config{} when you need an actual value. Use nil only with a nil-able type such as *Config.

Pointer versus value parameter

func UseValue(c Config)  {}  // Caller always provides a Config value.
func UsePointer(c *Config) {} // Caller can pass nil.

Use a pointer when absence needs to be represented or when shared mutation is intentionally needed. Otherwise, a value is often simpler and avoids nil checks.

Take Quiz

Cheat Sheet

// Struct value: never nil.
var c Config           // Config{host: "", port: 0}
// c == nil            // Invalid.

// Struct pointer: can be nil.
var p *Config          // nil
if p == nil {
    // No Config is present.
}

p = &Config{host: "localhost", port: 8080}
if p != nil {
    fmt.Println(p.host)
}
  • nil is valid for pointers, interfaces, slices, maps, channels, functions, and some unsafe pointer types.
  • Structs are value types and cannot be nil.
  • A struct's zero value is a valid struct with zero-valued fields.
  • Check a pointer for nil before accessing its fields.
  • Validate required struct fields explicitly.
  • config == (Config{}) only compiles when every field in Config is comparable.

FAQ

Why can err be compared to nil in Go?

error is an interface type, and interfaces can have the value nil. A non-nil error normally means an operation failed.

Can a Go struct ever be nil?

No. A struct value always exists. A pointer to a struct, such as *Config, can be nil.

How do I check whether a struct is empty in Go?

For a comparable struct, config == (Config{}) checks whether all fields have their zero values. Usually, explicit field validation is clearer and works even when fields are not comparable.

Should I use Config or *Config?

Use Config when configuration is required and has sensible zero values or validation rules. Use *Config when the entire configuration may be absent.

What happens when I access a field on a nil *Config?

The program panics at runtime because it attempts to dereference a nil pointer. Check the pointer first.

Is Config{} the same as nil?

No. Config{} is a real struct value whose fields contain their zero values. nil means no pointer, interface, map, slice, channel, or function value is present.

Why use float64 for a port?

Network ports are whole numbers, so an integer type such as int or uint16 is generally more appropriate. This does not affect whether Config can be nil.

Take Quiz

Mini Project

Description

Build a small configuration validator for a web server. It demonstrates the difference between an absent configuration pointer and a present configuration with invalid field values.

Goal

Return a useful error when configuration is missing or incomplete, and accept a valid server configuration.

Requirements

  • Define a Config struct with Host and Port fields.
  • Write a validateConfig function that accepts *Config.
  • Return an error when the pointer is nil.
  • Return an error when Host is empty.
  • Return an error when Port is outside the valid TCP port range.
  • Demonstrate validation with missing, invalid, and valid configurations.
Take Quiz

Keep learning

Related questions

View more