When to Use Value vs Pointer Receivers and Other Go Pitfalls You Must Avoid

This article examines common Go mistakes—including choosing between value and pointer receivers, misusing unnamed or named return values, returning nil interfaces, passing filenames instead of readers, and defer parameter evaluation—provides clear explanations, real‑world examples, and best‑practice recommendations to write more reliable Go code.

FunTester
FunTester
FunTester
When to Use Value vs Pointer Receivers and Other Go Pitfalls You Must Avoid

Error #42: Unsure Which Receiver Type to Use

Methods in Go can have either a value or a pointer receiver. Using a value receiver means modifications inside the method do not affect the original object, which can lead to unexpected behavior.

package main

import (
    "fmt"
)

type FunTester struct {
    Name  string
    Count int
}

// Value receiver
func (ft FunTester) Increment() {
    ft.Count += 1
}

// Pointer receiver
func (ft *FunTester) IncrementPointer() {
    ft.Count += 1
}

func main() {
    ft := FunTester{Name: "FunTester", Count: 0}
    ft.Increment()
    fmt.Printf("FunTester after value receiver = %+v
", ft) // Count still 0
    ft.IncrementPointer()
    fmt.Printf("FunTester after pointer receiver = %+v
", ft) // Count becomes 1
}

Impact: A value receiver cannot modify the original struct, which is problematic when the method is expected to change state.

Best Practices:

Use a pointer receiver if the method needs to modify the receiver's state.

Prefer a pointer receiver for large structs to avoid copying overhead.

If the struct contains non‑copyable fields (e.g., sync.Mutex), a pointer receiver is required.

Keep receiver types consistent across methods of the same type for readability and maintainability.

package main

import (
    "fmt"
)

type FunTester struct {
    Name  string
    Count int
}

// Pointer receiver ensures modifications persist
func (ft *FunTester) Increment() {
    ft.Count += 1
}

func main() {
    ft := &FunTester{Name: "FunTester", Count: 0}
    ft.Increment()
    fmt.Printf("FunTester after pointer receiver = %+v
", ft) // Count becomes 1
}

Output:

FunTester: after pointer receiver = &{Name:FunTester Count:1}

Error #43: Not Using Named Return Values

Named return values improve readability, especially when a function returns multiple values of the same type. However, misuse can cause unintended early returns or missed assignments.

package main

import (
    "fmt"
)

type FunTester struct {
    Name string
    Age  int
}

// No named return values
func NewFunTester(name string, age int) FunTester {
    return FunTester{Name: name, Age: age}
}

func main() {
    tester := NewFunTester("FunTester1", 25)
    fmt.Printf("FunTester: created = %+v
", tester)
}

Impact: When the function should modify several results, omitting names can make the code harder to understand and maintain.

Best Practices:

Use named returns when the function returns many values or when you want to document each result.

Avoid side effects by explicitly assigning to the named return variables inside the function.

Give descriptive names to aid readability.

package main

import (
    "fmt"
)

type FunTester struct {
    Name string
    Age  int
}

// Using named return values
func NewFunTester(name string, age int) (tester FunTester, err error) {
    if age < 0 {
        err = fmt.Errorf("FunTester: age cannot be negative")
        return
    }
    tester = FunTester{Name: name, Age: age}
    return
}

func main() {
    tester, err := NewFunTester("FunTester1", 25)
    if err != nil {
        fmt.Println("FunTester: creation error:", err)
        return
    }
    fmt.Printf("FunTester: created = %+v
", tester)
}

Output:

FunTester: created = {Name:FunTester1 Age:25}

Error #44: Unexpected Side Effects with Named Returns

If a named return variable is not explicitly assigned, the function may return a zero‑value unintentionally.

package main

import (
    "fmt"
)

type FunTester struct {
    Name string
    Age  int
}

func UpdateFunTester(t FunTester) (updated FunTester, err error) {
    if t.Age < 0 {
        err = fmt.Errorf("FunTester: age cannot be negative")
        return
    }
    t.Age += 1
    // Forgot to assign to 'updated'
    return
}

func main() {
    tester := FunTester{Name: "FunTester1", Age: 25}
    updatedTester, err := UpdateFunTester(tester)
    if err != nil {
        fmt.Println("FunTester: update error:", err)
        return
    }
    fmt.Printf("FunTester: updated = %+v
", updatedTester) // Age unchanged
}

Impact: Callers may receive an object that has not been updated, causing logic errors.

Best Practices:

Assign to all named return variables on every code path.

Use code reviews or tests to catch missing assignments.

Explicitly set the named return before returning.

package main

import (
    "fmt"
)

type FunTester struct {
    Name string
    Age  int
}

func UpdateFunTester(t FunTester) (updated FunTester, err error) {
    if t.Age < 0 {
        err = fmt.Errorf("FunTester: age cannot be negative")
        return
    }
    t.Age += 1
    updated = t
    return
}

func main() {
    tester := FunTester{Name: "FunTester1", Age: 25}
    updatedTester, err := UpdateFunTester(tester)
    if err != nil {
        fmt.Println("FunTester: update error:", err)
        return
    }
    fmt.Printf("FunTester: updated = %+v
", updatedTester) // Age becomes 26
}

Output:

FunTester: updated = {Name:FunTester1 Age:26}

Error #45: Returning a Nil Receiver

When a concrete nil pointer is returned as an interface value, the interface itself is non‑nil because it still holds type information, which can mislead callers.

package main

import (
    "fmt"
)

type FunTester interface {
    Run()
}

type FunTesterImpl struct { Name string }

func (ft *FunTesterImpl) Run() { fmt.Printf("FunTester: %s running
", ft.Name) }

func GetFunTester(condition bool) FunTester {
    if condition {
        return &FunTesterImpl{Name: "FunTester1"}
    }
    var ft *FunTesterImpl = nil
    return ft // Interface is non‑nil
}

func main() {
    tester := GetFunTester(false)
    if tester == nil {
        fmt.Println("FunTester: tester is nil")
    } else {
        fmt.Println("FunTester: tester is not nil")
        tester.Run()
    }
}

Impact: The caller may think the interface is usable and invoke methods, leading to a runtime panic.

Best Practices:

Return a plain nil when the function should indicate the absence of an implementation.

Check both the interface and its underlying concrete value before calling methods.

Prefer factory functions that hide the nil‑pointer details.

package main

import (
    "fmt"
)

type FunTester interface { Run() }

type FunTesterImpl struct { Name string }

func (ft *FunTesterImpl) Run() { fmt.Printf("FunTester: %s running
", ft.Name) }

func GetFunTester(condition bool) FunTester {
    if condition {
        return &FunTesterImpl{Name: "FunTester1"}
    }
    return nil // Explicit nil interface
}

func main() {
    tester := GetFunTester(false)
    if tester == nil {
        fmt.Println("FunTester: tester is nil")
    } else {
        fmt.Println("FunTester: tester is not nil")
        tester.Run()
    }
}

Output:

FunTester: tester is nil

Error #46: Using a Filename as a Function Parameter

Hard‑coding a filename limits a function to file‑based input, making testing difficult and reducing reusability.

package main

import (
    "fmt"
    "io"
    "io/ioutil"
    "os"
)

// Original version – accepts a filename
func ReadFunTesterFile(filename string) (string, error) {
    data, err := ioutil.ReadFile(filename)
    if err != nil {
        return "", err
    }
    return string(data), nil
}

Best Practice: Accept an io.Reader so the function can work with files, strings, network streams, etc.

package main

import (
    "fmt"
    "io"
    "io/ioutil"
    "os"
)

func ReadFunTester(r io.Reader) (string, error) {
    data, err := ioutil.ReadAll(r)
    if err != nil {
        return "", err
    }
    return string(data), nil
}

func main() {
    file, err := os.Open("FunTester.txt")
    if err != nil {
        fmt.Println("FunTester: open file error:", err)
        os.Exit(1)
    }
    defer file.Close()
    content, err := ReadFunTester(file)
    if err != nil {
        fmt.Println("FunTester: read error:", err)
        os.Exit(1)
    }
    fmt.Println("FunTester: file content =", content)
}

Output:

FunTester: file content = FunTester演示内容

Unit tests can now use strings.NewReader without touching the filesystem.

Error #47: Ignoring Defer Parameter Evaluation

Arguments to a defer statement are evaluated when the defer is declared, not when it runs. In loops this often leads to all deferred calls using the final loop variable value.

package main

import (
    "fmt"
    "os"
)

func main() {
    file, err := os.Create("FunTester_output.txt")
    if err != nil {
        fmt.Println("FunTester: cannot create file")
        return
    }
    defer file.Close()

    for i := 0; i < 3; i++ {
        defer fmt.Fprintf(file, "FunTester: record %d
", i) // i evaluated now
    }
    fmt.Println("FunTester: loop finished")
}

Impact: The deferred writes all record the same value (the final i), producing incorrect output.

Best Practices:

Capture the current loop variable in a closure before deferring.

Avoid using defer inside tight loops unless necessary.

Pass explicit values to the deferred function to control evaluation timing.

package main

import (
    "fmt"
    "os"
)

func main() {
    file, err := os.Create("FunTester_output.txt")
    if err != nil {
        fmt.Println("FunTester: cannot create file")
        return
    }
    defer file.Close()

    for i := 0; i < 3; i++ {
        func(n int) {
            defer func() {
                fmt.Fprintf(file, "FunTester: record %d
", n)
            }()
        }(i)
    }
    fmt.Println("FunTester: loop finished")
}

Resulting file content:

FunTester: record 0
FunTester: record 1
FunTester: record 2
These examples are part of a larger series on Go best practices and common pitfalls.
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Gobest practicesmethodsdeferpointer-receiverio.Readernamed-return-valuesnil-interface
FunTester
Written by

FunTester

10k followers, 1k articles | completely useless

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.