Pointer Receivers in Go Programming




Welcome to part 8 of the Go programming tutorial series, where we're talking about methods in Go lang.

So far, we've used our car type example, showing how we could calcuate the vehicle's speed using a value receiver method. What if we wanted to actually modify the object with a method? How might we do that? To do this, we use a pointer receiver. We can do this with a slight modification to our method. Let's suppose we have the same input, but, instead of a car with a top speed of 225, we just bolted in an amazing supercharger and our top speed is now 500 KM/H

func (c *car) new_top_speed(newspeed float64) {
	c.top_speed_kmh = newspeed
}

Note the *car in func (c *car). Now, we're modifying the struct itself via pointer. Now in the code we could do something like a_car.new_top_speed(500), and this would actually modify the object itself. Let's add that to the main function, making the full code up to this point:

package main

import "fmt"

const usixteenbitmax float64 = 65535
const kmh_multiple float64 = 1.60934

type car struct {
	gas_pedal uint16     //min: 0,      max: 65535    16bit
	brake_pedal uint16   //min: 0,      max: 65535
	steering_wheel int16 //min: -32768  max: 32768
	top_speed_kmh float64 //what's our top speed?
}

//gets a copy, receiver type
func (c car) kmh() float64 {
	return float64(c.gas_pedal) * (c.top_speed_kmh/usixteenbitmax) //top speed: 225. So do pedal * (225/65535)
}

//gets a copy, receiver type
func (c car) mph() float64 {
	return float64(c.gas_pedal) * (c.top_speed_kmh/kmh_multiple/usixteenbitmax)  //top speed: 140 mph.So do pedal * (225/1.60934/65535)
}

//modify the struct itself via pointer type
func (c *car) new_top_speed(newspeed float64) {
	c.top_speed_kmh = newspeed
}

func main() {
	//a_car := car{gas_pedal: 16535, brake_pedal: 0, steering_wheel: 12562}
	a_car := car{22314,0,12562,225.0}
	fmt.Println("gas_pedal:",a_car.gas_pedal, "brake_pedal:",a_car.brake_pedal,"steering_wheel:",a_car.steering_wheel)
	fmt.Println("Car is going",a_car.mph(),"MPH,",a_car.kmh(),"KMH, and top speed is",a_car.top_speed_kmh)
	a_car.new_top_speed(500)
	fmt.Println("Car is going",a_car.mph(),"MPH,",a_car.kmh(),"KMH, and top speed is",a_car.top_speed_kmh)
}

Running this, gives us:

gas_pedal: 22314 brake_pedal: 0 steering_wheel: 12562
Car is going 47.603494777765356 MPH, 76.6102082856489 KMH, and top speed is 225
Car is going 105.78554395058966 MPH, 170.24490730144197 KMH, and top speed is 500

As you can see, when we change the top speed of the car, the current speed based on the pedal position also changes. What if we changed top speed of the struct without the pointer? For example, if we did it in the mph function, which is just a value receiver:

func (c car) mph() float64 {
	c.top_speed_kmh = 25
	return float64(c.gas_pedal) * (c.top_speed_kmh/kmh_multiple/usixteenbitmax)  //top speed: 140 mph. So do 140/65535
}

What do you think will happen here?

The returned number will be changed, but will the actual top speed be changed? Running this gives:

gas_pedal: 22314 brake_pedal: 0 steering_wheel: 12562
Car is going 5.289277197529484 MPH, 76.6102082856489 KMH, and top speed is 225
Car is going 5.289277197529484 MPH, 170.24490730144197 KMH, and top speed is 500

As you can see, the current speed changed, but the top speed wasn't modified. This is of course because we were working with a copy of the object, not the object itself. Okay, what if we turned the mph and kmh method receivers to pointer receivers instead of value receivers? All we need to do is make car *car:

func (c *car) kmh() float64 {
	return float64(c.gas_pedal) * (c.top_speed_kmh/usixteenbitmax) //top speed: 225.308 mph. So do 225.308/65535
}

func (c *car) mph() float64 {
	return float64(c.gas_pedal) * (c.top_speed_kmh/kmh_multiple/usixteenbitmax)  //top speed: 140 mph. So do 140/65535
}
gas_pedal: 22314 brake_pedal: 0 steering_wheel: 12562
Car is going 47.603494777765356 MPH, 76.6102082856489 KMH, and top speed is 225
Car is going 105.78554395058966 MPH, 170.24490730144197 KMH, and top speed is 500

The only other difference with the new_top_speed pointer receiver was it didn't return anything, so it didn't need a return type (or a return of course).

Interesting output, everything looks the same. What if we do c.top_speed_kmh = 25 in the mph receiver?

func (c *car) mph() float64 {
	c.top_speed_kmh = 25
	return float64(c.gas_pedal) * (c.top_speed_kmh/kmh_multiple/usixteenbitmax)  //top speed: 140 mph. So do 140/65535
}
gas_pedal: 22314 brake_pedal: 0 steering_wheel: 12562
Car is going 5.289277197529484 MPH, 8.512245365072099 KMH, and top speed is 25
Car is going 5.289277197529484 MPH, 8.512245365072099 KMH, and top speed is 25

In this case, it changed not only the top speed, but also both MPH and KMH (even though we didn't modify the top speed in the kmh receiver), since the mph receiver is run before the kmh one.

At this point though, one must wonder, why would we ever use a value receiver? Why not always use the pointer receiver, even if you're not going to change something? Well, you could just always use pointer receivers, but, value receivers are better for basic types and small structs. This is because they're much cheaper to work with and can reduce the amount of overall garbage created. That said, if the struct is very large, a pointer is more likely to be more efficient since we're not creating a copy. To learn more, check out the golang.org doc FAQ on "Should I define methods on values or pointers?".

Interestingly enough, that doc suggests that, to keep things consistent, "if some of the methods of the type must have pointer receivers, the rest should too, so the method set is consistent regardless of how the type is used."

I am not really sure I agree with that sort of logic. Personally, I would test both. If the gains are insignificant by using value receivers where possible and you are using pointer receivers, sure, use all pointer receivers. If you can make sizeable gains by using value receivers where possible, however, I would personally use them.

Just for kicks, since I did it in the video, here's a function that could achieve similar results for us:

func newer_top_speed(c car, speed float64) car {
	c.top_speed_kmh = speed
	return c
}

func main(){
	a_car := car{gas_pedal: 65000, 
				brake_pedal: 0, 
				steering_wheel: 12561, 
				top_speed_kmh: 225.0}

	fmt.Println(a_car.kmh())
	fmt.Println(a_car.mph())
	//a_car.new_top_speed(500)
	a_car = newer_top_speed(a_car, 500)
	fmt.Println(a_car.kmh())
	fmt.Println(a_car.mph())

This will not likely be more efficient to do, so it's not advised, but still a good practice to just try to do it.

Alright, let's go back to our web app, we still have more to do!

The next tutorial:





  • Introduction to the Go Programming Language
  • Go Language Syntax
  • Go Language Types
  • Pointers in Go Programming
  • Simple Web App in Go Programming
  • Structs in the Go Programming Language
  • Methods in Go Programming
  • Pointer Receivers in Go Programming
  • More Web Dev in Go Language
  • Acessing the Internet in Go
  • Parsing XML with Go Programming
  • Looping in Go Programming
  • Continuing our Go Web application
  • Mapping in Golang
  • Mapping Golang sitemap data
  • Golang Web App HTML Templating
  • Applying templating to our Golang web app
  • Goroutines - Concurrency in Goprogramming
  • Synchronizing Goroutines - Concurrency in Golang
  • Defer - Golang
  • Panic and Recover in Go Programming
  • Go Channels - Concurrency in Go
  • Go Channels buffering, iteration, and synchronization
  • Adding Concurrency to speed up our Golang Web Application