Gilbert Nwaiwu
Gilbert Nwaiwu

Reputation: 728

Go struct method allows type mixing?

I have a simple struct with a single method:

type Person struct {
  name string
}

func (p Person) SetName(name string) {
  p.name = name
}

the output of the following:

dave := Person{}
dave.SetName("Dave")
fmt.Println(dave.name)

is going to be empty because the method receiver accepts a value(or more accurately creates a copy of the value you pass) so it will not modify your underlying value.

If I change the method to this:

func (p *Person) SetName(name string) {
  p.name = name
}

the output will be "Dave".

Now what I don't understand is am I not supposed to call the method on a pointer? So when initialing my object I should do this:

dave := &Person{}

instead of this:

dave := Person{}

Also using go's reflect package I was trying to find out the value of:

&Person

and found it to be *Person, which is fine. When I print the value though I don't get a memory location compared to when I print the value of a pointer to an int:

a := 4
fmt.Println(&a)

I keep reading the docs, asking questions on so but the more I learn the more I wonder if I'm missing something simple since a lot of people don't think all this is confusing.

Upvotes: 0

Views: 733

Answers (1)

kostix
kostix

Reputation: 55443

You're confusing calling a method on a pointer receiver with keeping a pointer to a value. That is, there is no inherent connection between calling SetName defined on a pointer receiver with the requirement to store a pointer to a memory holding a value of type Person vs storing Person directly.

When you have the declaration

var pers Person

and then call

pers.SetName("foo")

the compiler takes the address of the memory block occupied by the variable pers—just like if you were doing that by hand through applying the & operator to pers, and passes that address to the SetName function, so the call ends up being done like

(&pers).SetName("foo")

or, to say it differently, the SetName's receiver will be &pers—that is, the address of pers.

Now there's nothing preventing you from taking the address of pers and storing it elsewhere:

var ptr *Person = &pers

(you usually do not write such Java-style stuttering code but I'm doing this for extra clarity), and now you're able to call SetName right on the ptr value:

ptr.SetName("bar")

Now the compiler will directly use the value stored in the variable ptr to pass it as the method's receiver.

The final bit of this "puzzle" is that when you use the special syntax provided by Go—applying the address-taking operator & directly to a literal,—the compiler does something like this:

var __pers Person = Person{
    // The contents of your literal
}
var dave = &__pers

…and makes the __pers variable inaccessible by name (since it naturally has no name).

As you can see, this is no different from getting the pointer to a Person value's memory by hand.


You might want to consult this FAQ entry to gain more understanding of how the method sets on a type T and *T relate, and why the latter always includes the former but not vice-versa. Also read this.


Update: to explain the difference in how fmt.Println formats and outputs the values of type *int and *Person.

  • The former is printed as the base-16 string representation of an address of a memory location containing that int value.
  • The latter is rendered using a string representation resembling Go's syntax for taking a pointer to a literal: &{}.

This behaviour is as documented: fmt.Print and fmt.Println use the so-called default formatting rules for the arguments they are passed, and these rules depend on the types of the arguments.

Let's first consult the documentation on fmt.Println (run go doc fmt.Println):

func Println(a ...interface{}) (n int, err error)

Println formats using the default formats for its operands and writes to standard output. Spaces are always added between operands and a newline is appended. It returns the number of bytes written and any write error encountered.

(Emphasis mine.)

Now let's turn to the documentation of the fmt package itself (run go doc fmt):

<…>

Printing

The verbs:

General:

  • %v the value in a default format

    when printing structs, the plus flag (%+v) adds field names.

<…>

The default format for %v is:

bool:                    %t
int, int8 etc.:          %d
uint, uint8 etc.:        %d, %#x if printed with %#v
float32, complex64, etc: %g
string:                  %s
chan:                    %p
pointer:                 %p

For compound objects, the elements are printed using these rules, recursively, laid out like this:

struct:             {field0 field1 ...}
array, slice:       [elem0 elem1 ...]
maps:               map[key1:value1 key2:value2]
pointer to above:   &{}, &[], &map[]

A *Person is a pointer to a struct type, so it's rendered as

&{name}

where name is the contents of the name field; say, if it was assigned the string "Dave", the output would be &{dave}.

Upvotes: 6

Related Questions