ivan.sim
ivan.sim

Reputation: 9268

Golang Non-Struct Type Pointer Receiver

I created a custom type based on the Golang net.IP type. What surprised me is that a method declared with a pointer receiver to my custom type can't modify the value to which the receiver points.

The u variable in this code snippet remains nil after calling u.defaultIP(). The IP can be modified if I changed my custom type to a struct with an IP field and the method is defined with a pointer receiver to the struct. What am I missing? Executable example can be found here.

type userIP net.IP

func main() {
  var u *userIP
  u.defaultIP()
  fmt.Printf("%v\n", u) 
}

func (u *userIP) defaultIP() {
  defaultIP := userIP("127.0.0.1")
  u = &defaultIP
}

Upvotes: 7

Views: 2129

Answers (3)

user6169399
user6169399

Reputation:

Two Options:

1- With dereferencing: like this working code and using net.ParseIP("127.0.0.1")
(The Go Playground):

package main

import (
    "fmt"
    "net"
)

type userIP net.IP

func main() {
    var u userIP
    u.defaultIP()
    fmt.Println(u)
}

func (u *userIP) defaultIP() {
    *u = userIP(net.ParseIP("127.0.0.1"))
}

output:

[0 0 0 0 0 0 0 0 0 0 255 255 127 0 0 1]

2- Without dereferencing (The Go Playground):

package main

import (
    "fmt"
    "net"
)

type userIP net.IP

func main() {
    u := make(userIP, 4)
    u.defaultIP()
    fmt.Printf("%v\n", u)
}

func (u userIP) defaultIP() {
    u[0], u[1], u[2], u[3] = 127, 0, 0, 1
}

And note that net.IP is []byte, see net.IP Docs:

An IP is a single IP address, a slice of bytes. Functions in this package accept either 4-byte (IPv4) or 16-byte (IPv6) slices as input.

Upvotes: 0

Tom Scanlan
Tom Scanlan

Reputation: 113

You need to dereference the u before setting it's value.

From your example, change

defaultIP := userIP("127.0.0.1")
u = &defaultIP

to

*u = userIP("127.0.0.1")

For your example updated and working: https://play.golang.org/p/ycCLT0ed9F

Upvotes: 8

ivan.sim
ivan.sim

Reputation: 9268

TL;DR: The pointer receiver needs to be dereferenced before it's value can be set. This applies to both struct and non-struct types. In the case of struct types, the dereferencing is automatically done by the selector expression.

After digging around a bit further, I think this behaviour is caused by the fact that the pointer receiver is not the same pointer calling the method.

Running this code snippet shows that the u pointer in the main() function is different from that in the defaultIP() method. Essentially, I end up only modifying the u pointer in the defaultIP() method. Executable example can be found here.

func main() {
  var u *userIP         
  u.defaultIP()

  fmt.Printf("main(): address of pointer is %v\n", &u)
  fmt.Printf("main(): user IP address is %v\n", u)
}

type userIP net.IP

func (u *userIP) defaultIP() {  
  defaultIP := userIP("127.0.0.1")
  u = &defaultIP

  fmt.Printf("defaultIP(): address of pointer is %v\n", &u)
  fmt.Printf("defaultIP(): user IP address is %s\n", *u)
}

The correct way to do this is as pointed in Tom's answer i.e. dereference u in the defaultIP() method.

What puzzled me earlier was why would this example work if I wrapped the IP as a field in the struct? Running the code snippet shows that the two u pointers are indeed different, but the ip field is modified. Executable example can be found here.

func main() {
  u := &userInfo{}
  u.defaultIP()

  fmt.Printf("main(): address of pointer is %v\n", &u)
  fmt.Printf("main(): user IP address is %s\n", u.ip)
}

type userInfo struct{
  ip net.IP
}

func (u *userInfo) defaultIP() {
  u.ip = net.ParseIP("127.0.0.1")
  fmt.Printf("defaultIP(): address of pointer is %v\n", &u)
  fmt.Printf("defaultIP(): user IP address is %s\n", u.ip)
}

Turns out that this is caused by the selector expression (x.y). To quote the doc,

Selectors automatically dereference pointers to structs. If x is a pointer to a struct, x.y is shorthand for (x).y; if the field y is also a pointer to a struct, x.y.z is shorthand for ((*x).y).z, and so on. If x contains an anonymous field of type *A, where A is also a struct type, x.f is a shortcut for (*x.A).f.

So in my case, the u.ip expression dereferences u before modifying the ip field, which essentially translates to(*u).ip.

Upvotes: 2

Related Questions