1. Computing

Programming Golang Tutorial Seven

Functions and Methods

By

If you've come here first, start with the first Golang Tutorial One or Golang tutorial two on arrays, tutorial three on slices and tutorial four on pointers and structs.

In the previous Golang tutorial on anonymous functions and closures, I looked at anonymoous functions. In this tutorial, I'll look at methods which effectively turn structs into objects.

Don't know anything about Go? Here is an overview of Go.

.Now Go doesn't have classes like C++, C# or even Java, Those languages have classes with data and functions that act on that data and those are called methods.

But despite the lack of classes, Go does have methods, basically it makes it possible to do lightweight object oriented programming.

A method in Go is a function that has a specially defined first argument called a receiver. Syntactically the receiver occurs after func and before the name of the function. A receiver is just an instance of the type that it refers to. This shows a function and a method:

Normal function

func name( arguments) return type.

Method

func ( receiver) name( arguments) return type.

What is a Method Receiver?

It's an instance of a named type. It can be anything except interfaces or pointers. The Go documentation says you can't use a pointer type as the receiver but you can use a pointer to a type, it's a subtle distinction and we'll see it used later.

First though, let's have a look at an example. Here's a dog type struct with a name and a flag indicating whether it's a bitch or not. As always you can copy and paste all examples into the online Go Playground.

package main

import "fmt"

type dog struct {
  name string
  isabitch bool
}

func main() {
    fido := dog {"Fido", false }
    fmt.Println(fido)
}

That's just a struct, without any methods. Now let's make dog more of an object by creating bark() and playdead methods(). The receiver is just an instance of the dog type.


package main

import "fmt"

type dog struct {
  name string
  isabitch bool
}

func (d dog) bark() {
    fmt.Println(d.name,"Goes: 'Woof, Woof!'")
}

func (d dog) playdead() {
    fmt.Print(d.name, " rolls over, lies on ")
    if d.isabitch {
        fmt.Print("her back")
    } else {
        fmt.Print("his back")
    }
    fmt.Println(", with legs in the air... awwww!!")
}

func main() {
    fido := dog {"Fido", false }
    fido.bark()
    fido.playdead()
    lulu := dog{ "Lulu", true};
    lulu.playdead()
}

Run this and it outputs:

Fido Goes: 'Woof, Woof!'
Fido rolls over, lies on his back, with legs in the air... awwww!!
Lulu rolls over, lies on her back, with legs in the air... awwww!!

From an OOP perspective, bark() and playdead() are just two methods of object dog. Things to note here are

  • A method has access to the receiver's fields.
  • A method can't change the receiver's fields.

Let's prove the 2nd point by adding this method above func main() and calling it.

func (d dog) rename(newname string) {
    fmt.Println(d.name, " will forever more answer to" ,newname)
    d.name = newname
}

Add this after lulu.playdead()

lulu.rename("Lola")
lulu.bark()

However when you run it, despite the name change, you get:

Lulu will forever more answer to Lola
Lulu Goes: 'Woof, Woof!'

As I said earlier you can use a pointer to a type as a receiver so this modified rename function does work.

func (d *dog) rename(newname string) {
    fmt.Println((*d).name, " will forever more answer to" ,newname)
    (*d).name = newname
}

When run now, it outputs:

... Lola Goes: 'Woof, Woof!'

Lightweight OOP

Compared to OOP in other languages, you don't get inheritance or polymorphism, (Or at least not without a lot of complex code) but Go doesn't really need those. If you want to create say a showdog that's a type of dog, you could do it by composition, making dog a part of showdog. .

I've added this type:

type showdog struct {
  adog dog
  groomedby string
}

And this overloaded bark(). The different receiver's means there's no clash.

func (d showdog) bark() {
     fmt.Println(
          d.adog.name,
          "(Groomed by ",
          d.groomedby,
          ") Goes: 'Woof, Woof!'")
}

Now the first three lines of main() are this:

fido := showdog{ dog {"Fido", false },"Alphonse" }
fido.bark()
fido.adog.playdead()

And running it, gives this slightly different output:

Fido (Groomed by Alphonse ) Goes: 'Woof, Woof!'
...

You can view the full program listing.

Ooh That's Clever

Go has yet another trick up its sleeve, one of those "I didn't know you could do that...". . The Go compiler is smart enough to know that when you write d *dog, instead of accessing d by (*d), it will still understand you using d instead. No really! Try this: Here's the rename function as it is:

func (d *dog) rename(newname string) {
    fmt.Println((*d).name, " will forever more answer to" ,newname)
    (*d).name = newname
}

And here it is after changing the last two lines:

func (d *dog) rename(newname string) {
    fmt.Println(d.name, " will forever more answer to" ,newname)
    d.name = newname
}

Try that. It works and is easier to read and understand...

In the next Google Go tutorial 8, I'll look at maps (I had intended this tutorial to be about maps but then realized that I'd not covered methods) and explain how to use them.

  1. About.com
  2. Computing
  3. C / C++ / C#
  4. All about Google Go
  5. Learn to program Go Tutorial Seven - Functions and Methods

©2014 About.com. All rights reserved.