As we all know, the difference between a pointer and a value when defining a Go struct method is that when modifying a property value within a method, the value defined method changes only within the method, while the pointer does not have this limitation.
This article would be boring if it ended there, so I’m going to take you to a boring but thought-provoking experiment.
Before we get started, let’s write some simple code to run through what we’ve already mentioned and familiarize ourselves with some of the coding rules for the upcoming experiment code. By the way, the following code was written in 2021.08 and the Go version is 1.16.5. By the time you read this article, Go has been updated many times and may not be applicable. No more nonsense, on the code:
package main
import "fmt"
type Foo struct {
val int
}
/** * I've defined two Set methods: * P = Pointer and V = Value ** I've added a callBy to trace the call chain */
func (f *Foo) SetP(v int, callBy string) {
f.val = v
fmt.Printf("In SetP(): call by:%s\tval:%d\n", callBy, f.val)
}
func (f Foo) SetV(v int, callBy string) {
f.val = v
fmt.Printf("In SetV(): call by:%s\tval:%d\n", callBy, f.val)
}
func main(a) {
f := Foo{0}
fmt.Printf("In main(): val:%d\n", f.val)
fmt.Println("= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =")
f.SetP(1."main")
fmt.Printf("In main(): after f.SetP(1): val:%d\n", f.val)
fmt.Println("= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =")
f.SetV(2."main")
fmt.Printf("In main(): after f.SetV(2): val:%d\n", f.val)
fmt.Println("= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =")}Copy the code
Running results:
In main(): val:0
=====================================
In SetP(): call by:main val:1
In main(): after f.SetP(1): val:1
=====================================
In SetV(): call by:main val:2
In main(): after f.SetV(2): val:1
Copy the code
As expected, changes to attributes within methods defined by values do not carry impact externally.
Next, let’s start our experiment
What happens if the method is a doll?
In our daily development, we often encounter a method that calls another method. What happens if a property is changed in the called method?
Dolls will have four cases: PV, VP, VV, PP (the actual situation may also appear more layers of dolls, but here we only need to understand one layer, the rest can be understood by mathematical induction.) Add four Set methods to the code:
func (f *Foo) SetPV(v int, callBy string) {
f.SetV(v+1, callBy+"->SetPV")
fmt.Printf("In SetPV(): call by:%s\tval:%d\n", callBy, f.val)
f.val = v
}
func (f Foo) SetVP(v int, callBy string) {
f.SetP(v+1, callBy+"->SetVP")
fmt.Printf("In SetVP(): call by:%s\tval:%d\n", callBy, f.val)
f.val = v
}
func (f *Foo) SetPP(v int, callBy string) {
f.SetP(v+1, callBy+"->SetPP")
fmt.Printf("In SetPP(): call by:%s\tval:%d\n", callBy, f.val)
f.val = v
}
func (f Foo) SetVV(v int, callBy string) {
f.SetV(v+1, callBy+"->SetVV")
fmt.Printf("In SetVV(): call by:%s\tval:%d\n", callBy, f.val)
f.val = v
}
Copy the code
Then add to main() :
func main(a) {
f := Foo{0}
fmt.Printf("In main(): val:%d\n", f.val)
fmt.Println("= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =")
f.SetP(1."main")
fmt.Printf("In main(): after f.SetP(1): val:%d\n", f.val)
fmt.Println("= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =")
f.SetV(2."main")
fmt.Printf("In main(): after f.SetV(2): val:%d\n", f.val)
fmt.Println("= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =")
f.SetPV(3."main")
fmt.Printf("In main(): after f.SetPV(3): val:%d\n", f.val)
fmt.Println("= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =")
f.SetVP(4."main")
fmt.Printf("In main(): after f.SetVP(4): val:%d\n", f.val)
fmt.Println("= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =")
f.SetVV(5."main")
fmt.Printf("In main(): after f.SetVV(5): val:%d\n", f.val)
fmt.Println("= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =")
f.SetPP(6."main")
fmt.Printf("In main(): after f.SetPP(6): val:%d\n", f.val)
}
Copy the code
Execution Result:
In main(): val:0
=====================================
In SetP(): call by:main val:1
In main(): after f.SetP(1): val:1
=====================================
In SetV(): call by:main val:2
In main(): after f.SetV(2): val:1
=====================================
In SetV(): call by:main->SetPV val:4
In SetPV(): call by:main val:1
In main(): after f.SetPV(3): val:3
=====================================
In SetP(): call by:main->SetVP val:5
In SetVP(): call by:main val:5
In main(): after f.SetVP(4): val:3
=====================================
In SetV(): call by:main->SetVV val:6
In SetVV(): call by:main val:3
In main(): after f.SetVV(5): val:3
=====================================
In SetP(): call by:main->SetPP val:7
In SetPP(): call by:main val:7
In main(): after f.SetPP(6): val:6
Copy the code
Make a table:
methods | When the main() call endsf.val 值 |
Layer 1 method name/End of layer 2 methodf.val 值 |
Layer 2 method name/method endf.val 值 |
---|---|---|---|
SetPV() | 3 | SetPV(3) / 1 | SetV(3+1) / 4 |
SetVP() | 3 | SetVP(4) / 5 | SetP(4+1) / 5 |
SetVV() | 3 | SetVV(5) / 3 | SetV(5+1) / 6 |
SetPP() | 6 | SetPP(6) / 7 | SetP(6+1) / 7 |
We conclude that changes to attributes are retained only if the entire invocation link is a pointer defined method, otherwise they will only be valid within the method, in accordance with the original rules.
At this point you might think the article is over, but it isn’t. Let’s focus on SetVP() :
func (f Foo) SetVP(v int, callBy string) {
f.SetP(v+1, callBy+"->SetVP") SetVP() = SetVP()
fmt.Printf("In SetVP(): call by:%s\tval:%d\n", callBy, f.val)
f.val = v
}
Copy the code
Modify SetPP(), which looks similar:
func (f *Foo) SetPP(v int, callBy string) {
f.SetP(v+1, callBy+"->SetPP") // This is also a pointer
fmt.Printf("In SetPP(): call by:%s\tval:%d\n", callBy, f.val)
// f.val = v /* comment out this line */
}
Copy the code
After executing it, it modifies values not just inside SetPP()! Does (f Foo) cause a copy of the internal (f *Foo) method?
Print out the pointer to be sure!
func (f *Foo) SetP(v int, callBy string) {
fmt.Printf("In SetP(): &f=%p\t&f.val=%p\n", &f, &f.val)
f.val = v
fmt.Printf("In SetP(): call by:%s\tval:%d\n", callBy, f.val)
}
/ /... If you omit any other method, it's the same thing, just change the name
func (f Foo) SetVP(v int, callBy string) {
fmt.Printf("In SetVP(): &f=%p\t&f.val=%p\n", &f, &f.val)
f.SetP(v+1, callBy+"->SetVP")
fmt.Printf("In SetVP(): call by:%s\tval:%d\n", callBy, f.val)
f.val = v
}
func main(a) {
f := Foo{0}
fmt.Printf("In main(): val:%d\n", f.val)
/ /... Leave out other unmodified areas
}
Copy the code
Take a look at the result (I’ve highlighted the three lines to focus on) :
In main(): val:0 ⚠️ In main(): &f=0x14000124008 &f.val=0x14000124008 ==================================================== In SetP(): &f=0x14000126020 &f.val=0x14000124008 In SetP(): call by:main val:1 In main(): after f.SetP(1): val:1 ==================================================== In SetV(): &f=0x14000124010 &f.val=0x14000124010 In SetV(): call by:main val:2 In main(): after f.SetV(2): val:1 ==================================================== In SetPV(): &f=0x14000126028 &f.val=0x14000124008 In SetV(): &f=0x14000124018 &f.val=0x14000124018 In SetV(): call by:main->SetPV val:4 In SetPV(): call by:main val:1 In main(): after f.SetPV(3): Val: 3 = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = ⚠ ️ In SetVP () : &f= 0x14000124030&f al=0x14000124030 ⚠️ In SetP(): &f= 0x14000126030&f al=0x14000124030 In SetP(): call by:main->SetVP val:5 In SetVP(): call by:main val:5 In main(): after f.SetVP(4): val:3 ==================================================== In SetVV(): &f=0x14000124038 &f.val=0x14000124038 In SetV(): &f=0x14000124060 &f.val=0x14000124060 In SetV(): call by:main->SetVV val:6 In SetVV(): call by:main val:3 In main(): after f.SetVV(5): val:3 ==================================================== In SetPP(): &f=0x14000126038 &f.val=0x14000124008 In SetP(): &f=0x14000126040 &f.val=0x14000124008 In SetP(): call by:main->SetPP val:7 In SetPP(): call by:main val:7 In main(): after f.SetPP(6): val:6Copy the code
It can be found:
- Whether it’s
(f Foo)
or(f *Foo)
, within the method,f
It’s all copied itself - Property address
2.1. In the case of a pointer method, the property address inherits from the caller 2.2. In the case of a value method, the property address is the newly created spatial address
As to whether the doll call will lead to memory surge, there is no discussion here, interested can Go to their own information or look at the underlying implementation of Go.
conclusion
Before this article finally comes to an end, let’s summarize what meaningful hints this boring experiment has for our actual development:
- If you need to make temporary changes to a property of a method (for example, if the current method needs to call another method, and the target method reads the property value and you are not allowed to change the target method), then you should define the method as value passed
- If you have a method defined as value passed, remember that any changes you make directly or to the doll in the method will not be passed up (to the caller), but it will be passed down
This article was first published on my blog: yian.me/blog/what-i…