-4

I am doing a tour of go language, and I have a question about pointers.

Example code (https://tour.golang.org/methods/19):

package main

import (
    "fmt"
    "time"
)

type MyError struct {
    When time.Time
    What string
}

func (e *MyError) Error() string {
    return fmt.Sprintf("at %v, %s",
        e.When, e.What)
}

func run() error {
    return &MyError{
        time.Now(),
        "it didn't work",
    }
}

func main() {
    if err := run(); err != nil {
        fmt.Println(err)
    }
}

In this case it is using *MyError and &MyError, but I try to remove the * and & and it works correctly. Why are they using pointers in this example? What is the difference with normal variables? When should I use pointers or not?

David
  • 923
  • 8
  • 19

2 Answers2

3

"When should I use pointers?" is a very large question without a simple answer. Pointers are a way of passing a reference to a value, rather than a value itself, around, allowing you to modify the original value or "see" modifications to that value. It also prevents copying, which can be a performance improvement in very limited circumstances (do not pass pointers around all the time because it might be a performance improvement). Finally, pointers also let you represent "nothingness", which each pointer being able to be nil. This is both a blessing and a curse as you must check if each pointer is nil before accessing it, though.

In your specific example, the reason why returning &MyError works is because your Error() function operates on a value of *MyError (a pointer to MyError), rather than on a value of MyError itself. This means that *MyError implements the Error interface and is thus assignable to the error type, and so it can be returned from any function that expects an error as a return value.

Returning MyError wouldn't work on its own because MyError is not a *MyError. Go does some helpful things when dealing with function receivers: It will let you call any method on a MyError or *MyError if the receiver is *MyError, but it will only let you call methods on a *MyError if the type is *MyError - That is, Go will not "create" a pointer for you out of thin air.

If you were to remove * from func (e* MyError), you would be telling Go that Error() works on any instance of a MyError, which means that both *MyError and MyError would fulfill that contract. That's why both of the following are valid when you don't use a pointer receiver:

func (e MyError) Error() string {}

var _ error = MyError{} // Valid
var _ error = &MyError {}
Dan
  • 10,282
  • 2
  • 37
  • 64
2

In this particular case, using pointers will not make a difference. Here's one way to look at it:

In Go, all variables are passed by value. That means:

type T struct {...}

func f(value T) {..}

f(t)

Above, t is passed as value. That means when f is called, the compiler creates a copy of t and passed that to f. Any modifications f makes to that copy will not affect the t used to call f.

If you use pointers:

func f(value *T) {...}

f(&t)

Above, the compiler will create a pointer pointing to t, and pass a copy of that to f. If f makes changes to value, those changes will be made on the instance of t used to call f. In other words:

type T struct {
  x int
}

func f(value T) {
   value.x=1
}

func main() {
   t:=T{}
   f(t)
   fmt.Println(t.x)
}

This will print 0, because the modifications made by f is done on a copy of t.

func f(value *T) {
   value.x=1
}

func main() {
   t:=T{}
   f(&t)
   fmt.Println(t.x)
}

Above, it will print 1, because the call to f changes t.

Same idea applies to methods and receivers:

type T struct {
   x int
}

func (t T) f() {
   t.x=1
}

func main() {
   t:=T{}
   t.f()
   fmt.Println(t.x)
}

Above program will print 0, because the method modifies a copy of t.

func (t *T) f() {
   t.x=1
}

func main() {
   t:=T{}
   t.f()
   fmt.Println(t.x)
}

Above program will print 1, because the receiver of the method is declared with a pointer, and calling t.f() is equivalent to f(&t).

So, use pointers when passing arguments or when declaring methods if you want to modify the object, or if copying the object would be too expensive.

This is only a small part of the story about pointer arguments.

Burak Serdar
  • 46,455
  • 3
  • 40
  • 59