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!