1

I wanted to have a custom type based on a basic type and be able to set its value by calling a pointer receiver.

When I run the following program:

package main

import (
    "fmt"
    "strconv"
)

type FooInt int
func (fi *FooInt) FromString(i string) {
    num, _ := strconv.Atoi(i)
    tmp := FooInt(num)
    fi = &tmp
}

func main() {
    var fi *FooInt
    fi.FromString("5")
    fmt.Printf("%v\n", fi)
}

I receive <nil>. Why doesn't the pointer declared in main() change its value to the address of tmp?

Here is a Go playground link.

rocku
  • 141
  • 1
  • 5

2 Answers2

7

All arguments–including the receiver–is a copy inside the function/method. You can only modify the copy.

This applies to pointers too: the receiver value (the fi pointer) is a copy, so you can't modify the original pointer, only the copy.

Usually the receiver is a non-nil pointer, and you modify the pointed value–which results in the original pointed value changed.

In your case you either have to return the pointer and assign the return value:

func (fi *FooInt) FromString(i string) *FooInt {
    num, _ := strconv.Atoi(i)
    tmp := FooInt(num)
    return &tmp
}

func main() {
    var fi *FooInt
    fi = fi.FromString("5")
    fmt.Printf("%v %v\n", fi, *fi)
}

This will output (try it on the Go Playground):

0xc0000b4020 5

Or pass a non-nil pointer to what you want to change, in your case it would be of type **FooInt

func (fi *FooInt) FromString(i string, p **FooInt) {
    num, _ := strconv.Atoi(i)
    tmp := FooInt(num)
    *p = &tmp
}

func main() {
    var fi *FooInt
    fi.FromString("5", &fi)
    fmt.Printf("%v %v\n", fi, *fi)
}

This outputs the same. Try it on the Go Playground.

But easiest would be to just ensure the receiver is not nil, so the pointed value can simply be modified:

func (fi *FooInt) FromString(i string) {
    num, _ := strconv.Atoi(i)
    *fi = FooInt(num)
}

func main() {
    var fi *FooInt
    fi = new(FooInt)
    fi.FromString("5")
    fmt.Printf("%v %v\n", fi, *fi)
}

Output is the same. Try this one on the Go Playground.

icza
  • 389,944
  • 63
  • 907
  • 827
  • Thanks for your thorough explanation, that makes total sense. I was able to patch the code which I was working on just like I intended to. – rocku May 12 '20 at 22:19
4

The syntax:

func (fi *FooInt) FromString(i string) {
    // ...
}

is partly syntactic sugar for:

func FromString(fi *fooInt, i string) {
    // ...
}

That is, the fi parameter here is an ordinary local variable. If you assign to it, you replace the pointer value that the caller supplied, rather than writing through the pointer value that the caller supplied. Hence you need to use:

    *fi = FooInt(num)

in the body of the function. However, now the caller must pass a non-nil pointer:

var fi FooInt
fi.FromString("5")

for instance.

Here is a complete example, including a method by which you can call the FromString function and pass an explicit pointer.

(I say partly syntactic sugar because this defines FromString as a receiver function or method, which can only be done using this syntax. So the syntax is required—it's not an alternative to some other syntax, as people sometimes mean when using the phrase "syntactic sugar".)

torek
  • 448,244
  • 59
  • 642
  • 775