Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Golang Non-Struct Type Pointer Receiver

Tags:

go

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
}
like image 326
ivan.sim Avatar asked Aug 24 '16 03:08

ivan.sim


People also ask

What is pointer receiver in Golang?

You can declare methods with pointer receivers. This means the receiver type has the literal syntax *T for some type T . (Also, T cannot itself be a pointer such as *int .) For example, the Scale method here is defined on *Vertex .

What is receiver type in Golang?

Introduction. A method is just a function with a special receiver type between the func keyword and the method name. The receiver can either be a struct type or non-struct type. The syntax of a method declaration is provided below. func (t Type) methodName(parameter list) { }

When would you use a pointer receiver?

There are two reasons to use a pointer receiver. The first is so that the method can modify the value that its receiver points to. The second is to avoid copying the value on each method call. This can be more efficient if the receiver is a large struct, for example.

Is it possible to call methods that are declared with a value receiver using a pointer?

you can mix and match methods with value receivers and methods with pointer receivers, and use them with variables containing values and pointers, without worrying about which is which. Both will work, and the syntax is the same.


2 Answers

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

like image 158
Tom Scanlan Avatar answered Sep 22 '22 00:09

Tom Scanlan


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.

like image 44
ivan.sim Avatar answered Sep 21 '22 00:09

ivan.sim