Thursday, October 31, 2013

Learning more about pointers in Go (Golang)

I really enjoy the Go book (found here) and the concise chapters and examples it provides. Today I started reading through chapter 9, specifically the section on embedded types. Embedded types allow Go structs to have an "is a" relationship instead of just a "has a" relationship.  I changed up the examples a little so I could make sure I was learning the concept and not just typing in some one else's code.  Take a look at the code for the two structs, Person and Android.

type Person struct {
 Name string
}

type Android struct {
 Person
 Model string
}

Person has a name and Android "is a" Person because I've added the type with no name(that's the embedded type part).  Model is a standard string property just like name is on Person.

I made the method "Talk" to go with the Person struct:

func (p *Person) Talk(TalkingTo *Person) {
 fmt.Printf("%v said, 'Hello there, %v.'\n", p.Name, TalkingTo.Name)
}

By working through the exercise, I was able to understand more about how pointers work.  Everything was going great.  Bob could talk to Frank and Frank could talk to Bob:

 Bob := Person{"Bob"}
 Frank := Person{"Frank"}

 Bob.Talk(&Frank)
 Frank.Talk(&Bob)

The Talk method accepts an argument of type Person pointer and I was able to use the & to get a pointer to the instance of the Person struct.  I was getting it.  If you ask for a pointer as a parameter, you can get that pointer by putting an & in front of a struct variable.  & means "give me a pointer to what follows."

Next I had Marty (an Android) talk to Bob(a Person):

 Marty := new(Android)
 Marty.Name = "Marty"
 Marty.Model = "T1000"

 Marty.Talk(&Bob)

No problem. Marty might be an Android but since it has a Person embedded type, it can talk like a Person struct can.

Next up I created an android called Arnold:

Arnold := Android{Person{"Arnold"}, "T1001"}

I also gave the android struct its own means of communication, called "Beep":

func (a *Android) Beep(BeepAt *Android) {
 fmt.Printf("%v beeped, 101010100001 at %v\n", a.Name, BeepAt.Name)
}

It's the same as the Talk method on the Person struct, just made for the Android struct.  The problem occurred when I tried to make Marty Beep at Arnold. This code generated an error:

Arnold.Beep(&Marty)

The error was:
cannot use &Marty (type **Android) as type *Android in function argument

The "**" should have been a tip off, but it stumped me for a while. I couldn't figure out why Arnold couldn't beep at Marty. Everybody else was talking just fine. What was the difference?  To my .Net/C# eyes, all of the variables were being instantiated the same way.  Then I realized that the Marty variable of type Android wasn't created the way the others were, it was using the new keyword.  In .Net new initializes the variable and fires off the constructor logic.  But that's not how things work in Go with the keyword new.  I did some reading and here's what I found:

In Go, the new keyword, "...allocates memory for all the fields, sets each of them to their zero value and returns a pointer."

By passing the Marty variable with an & in front of Marty, I was asking Go to pass a pointer to a pointer.  The function only accepts a pointer.  Marty was a variable that was a pointer to a struct instance of type Android while Arnold was just an instance of a struct of type Android.  The fix was simple once I understood what was going on.  I removed the & from Marty and the compiler was happy.  And more importantly, Arnold got to beep at Marty.

Arnold.Beep(Marty)
Arnold beeped, 101010100001 at Marty

I don't have pointers all figured out yet, but I'm closing in on them. Full source can be found here: Til next time.

No comments:

Post a Comment