[Question] Overwrite Pointer Receiver in Method?

965 views
Skip to first unread message

Gyu-Ho Lee

unread,
Mar 22, 2014, 1:49:12 PM3/22/14
to golan...@googlegroups.com

We pass pointer to modify receiver as in this code:
http://play.golang.org/p/kDruwAB1gY

But why overwriting(assigning) to receiver does not have any effect?
I know we can just return the modified struct and reassign the returned value.
But curious about how it works.

case5 := &Person{name: "No Change", age: 10}

fmt.Printf("Before passing Pointer to MethodPt: %+v\n", case5)
// Before passing Pointer to MethodPtR: &{name:No Change age:10}

case5 = case5.MethodPtR()

fmt.Printf("After passing Pointer to MethodPt: %+v\n", case5)
// After passing Pointer to MethodPt: &{name:MethodPt Changed! age:1000}


The full code is here: http://play.golang.org/p/8B7p7U14MJ

type Person struct {
name string
age  int
}

func (p Person) MethodNoPt() {
p = Person{
name: "MethodNoPt Changed!",
age:  1000,
}
}

func (p *Person) MethodPt() {
p = &Person{
name: "MethodPt Changed!",
age:  1000,
}
}


/********************************************/
case1 := Person{name: "No Change", age: 10}

fmt.Printf("Before passing Non-Pointer to MethodNoPt: %+v\n", case1)
// Before passing Non-Pointer to MethodNoPt: {name:No Change age:10}

case1.MethodNoPt()

fmt.Printf("After passing Non-Pointer to MethodNoPt: %+v\n", case1)
// After passing Non-Pointer to MethodNoPt: {name:No Change age:10}
/********************************************/

/********************************************/
case2 := Person{name: "No Change", age: 10}

fmt.Printf("Before passing Pointer to MethodNoPt: %+v\n", case2)
// Before passing Pointer to MethodNoPt: {name:No Change age:10}

(&case2).MethodNoPt()

fmt.Printf("After passing Pointer to MethodNoPt: %+v\n", case2)
// After passing Pointer to MethodNoPt: {name:No Change age:10}
/********************************************/

/********************************************/
case3 := Person{name: "No Change", age: 10}

fmt.Printf("Before passing Non-Pointer to MethodPt: %+v\n", case3)
// Before passing Non-Pointer to MethodPt: {name:No Change age:10}

case3.MethodPt()

fmt.Printf("After passing Non-Pointer to MethodPt: %+v\n", case3)
// After passing Non-Pointer to MethodPt: {name:No Change age:10}
/********************************************/

/********************************************/
case4 := &Person{name: "No Change", age: 10}

fmt.Printf("Before passing Pointer to MethodPt: %+v\n", case4)
// Before passing Pointer to MethodPt: &{name:No Change age:10}

case4.MethodPt()

fmt.Printf("After passing Pointer to MethodPt: %+v\n", case4)
// After passing Pointer to MethodPt: &{name:No Change age:10}
/********************************************/

Gyepi SAM

unread,
Mar 22, 2014, 2:33:56 PM3/22/14
to Gyu-Ho Lee, golan...@googlegroups.com
On Sat, Mar 22, 2014 at 10:49:12AM -0700, Gyu-Ho Lee wrote:
> case5 := &Person{name: "No Change", age: 10}
>
> fmt.Printf("Before passing Pointer to MethodPt: %+v\n", case5)
> // Before passing Pointer to MethodPtR: &{name:No Change age:10}
>
> case5 = case5.MethodPtR()
>
> fmt.Printf("After passing Pointer to MethodPt: %+v\n", case5)
> // After passing Pointer to MethodPt: &{name:MethodPt Changed! age:1000}

According to the spec, which bears careful reading, this is OK:

A method call x.m() is valid if the method set of (the type of) x contains m
and the argument list can be assigned to the parameter list of m. If x is
addressable and &x's method set contains m, x.m() is shorthand for (&x).m():

However, Effective Go says:

The rule about pointers vs. values for receivers is that value methods can be
invoked on pointers and values, but pointer methods can only be invoked on
pointers.

It seems that the latter may need to be updated.

-Gyepi

Gyu-Ho Lee

unread,
Mar 22, 2014, 2:49:45 PM3/22/14
to golan...@googlegroups.com
I see. Thanks!

bga...@golang.org

unread,
Mar 23, 2014, 6:27:05 AM3/23/14
to golan...@googlegroups.com
On Saturday, March 22, 2014 1:49:12 PM UTC-4, Gyu-Ho Lee wrote:

We pass pointer to modify receiver as in this code:
http://play.golang.org/p/kDruwAB1gY

But why overwriting(assigning) to receiver does not have any effect?

Arguments are always passed by value.
The receiver is just an argument.
Start with this example:

This works the way you expect.  The argument is passed by value.  Changing the argument's value does not change the corresponding variable in the caller.
Now adjust that example to have a pointer:

We can't change the value of the variable passed in.
If the variable passed in is a pointer, we *can* change the thing that it is pointing to.
However, we can't change *what* it is pointing to - that would be changing the argument's value, and that won't get reflected in the caller.

 

Gyu-Ho Lee

unread,
Mar 24, 2014, 1:43:58 AM3/24/14
to golan...@googlegroups.com
I see. Thanks a lot bgarcia

jor...@etsy.com

unread,
Mar 24, 2014, 11:23:18 AM3/24/14
to golan...@googlegroups.com
this example has way too much code in it; it took me a while to figure out which case you're talking about.  You're actually talking about case 4, not case 5.  Why case 5 works is clearly understood, but case 4 is the much more important case.  The reason this case is so important is how it interacts with interfaces.

As you wrote in your example, everything is passed by value.  I think what's tripping you up is that you're not also considering that the receiver of the method is also passed by value.

Let's talk about case 4.  In the calling context (i.e., main), the variable `case4` is of type *Person; a pointer to a person.  Before doing anything else, we have two things:

    - a Person struct.
    - a pointer to that person struct.  (pointer one)

Next, we'll call the method `MethodPt`.  When we call `MethodPt`, we're actually making a copy of the pointer we had before (pointer one).  That means that inside the body of `MethodPt`, we actually have a *different* pointer; let's call it pointer two.  The next thing you do is *reassign* pointer two using an assignment and a struct literal.  This creates an additional Person struct, instead of modifying the Person struct we already had, and sets pointer two to point to this new Person struct.  So instead of modifying the thing that your pointer pointed to, you created something new, and then pointed that pointer at the new thing.

So by line 28 of your original example, we have:

    - a Person struct in main (person one)
    - a pointer to person one in main (pointer one)
    - a Person struct inside of MethodPt (person two)
    - a pointer to person two in MethodPt (pointer two)

After calling `MethodPt`, you assigned the result, which was pointer two, to pointer one.  You are now left with this in main:

    - person one has been orphaned.  Nothing points to it.  It still exists, but it's unreachable.  At some undefined point in the future, it will be picked up by the garbage collector.
    - a pointer to person two.

You never actually changed the Person struct you started out with; you just created a new one.

If you want to completely overwrite that person in place, you have to *dereference* the pointer inside of `MethodPt` with the dereference (*) operator.  The `MethodPt` definition thus becomes:

func (p *Person) MethodPt() {
*p = Person{
name: "MethodPt Changed!",
age:  1000,
}
}


And now this is actually polymorphic.  Now *any* type can implement MethodPt(), because it's not tied to some specific return type.  This allows you to define an interface around that method.



On Saturday, March 22, 2014 1:49:12 PM UTC-4, Gyu-Ho Lee wrote:
Reply all
Reply to author
Forward
0 new messages