Sunday, June 5, 2011

Structs in Go - instead of classes in Object Oriented Programming


Go isn’t object oriented. It is procedural. However, a good part of the programming world these days is used to object oriented programming. So in the next few sections we shall look at how procedural programming in Go gives you everything that you used to do with OOPs, but only easier. ‘Easier’ is however a relative concept. If you are used to classes and inheritance and polymorphism, learning Go’s way of doing things, would require a mindset change. As I had to too. But once you grasp it, you can see that it is powerful too, and maybe even simpler, but surely faster. Let’s get to it then.

Is Go object-oriented.

A small early indication for those used to OOPS: first thing to remember, Go does not have a 'class' keyword. A struct is what you would use for a parallel concept in Go. Languages like Java, C#, C++ and others also have structs. Like many of them support methods for structs, Go also does. So that shouldn’t be difficult to grasp. But there is a difference.

In some object oriented languages, methods are composed within a class or struct. In Go, they are ‘associated’ with a struct.

Partial code: In Java
class House {
    public String getHouseName() {  //method defined within class
        //implementation
    }
}

Partial code: In Go
type House struct { }

func (h House) GetHouseName() string { } //method defined outside of struct, but works on House

Now let’s learn how to create structs and their specialities in Go that allows us to get all the goodness of object oriented programming without actually needing it. A struct is defined with the type and struct keywords.

type my_struct_name struct { }

type Rectangle struct { }

type Vehicle struct { }

type Vehicle1_Car struct { }

All of the above are valid definitions since they follow the Go variable naming conventions. The ones below are not valid.

type Hash# struct {} //cannot have special characters
type 0struct struct {} //cannot start with a number

Next up, structs can contain other data. So a struct, like a class, allows you to define the contents of the real world item that you are trying to represent and layout memory accordingly. Here are some valid examples.

type my_struct_name struct {
    i int 
    j int 
    s string
}

type Rectangle struct {
    length, width int //you can define multiple items of the same type on the same line separated by commas
    area float64
}

Now on to using structs in code. We shall retain a part of our Rectangle class and print it.

package main

import "fmt"

type Rectangle struct {
    length, width int 
}

func main() {
    r := Rectangle{}  
    fmt.Println("Default rectangle is: ", r) //print default zero-ed value
}

Default rectangle is: {0 0}

There is one important thing to notice in the output: the values of the variables within the struct are zero-ed, i.e. an int will be 0, a string will be empty, etc. So in effect, the struct also has a zero-ed initialized value depending on the type of its constituents.

In the next example, we look at different ways of initializing a struct, setting values for variables within it, and also default printing.

package main

import "fmt"

type Rectangle struct {
    length, width int 
    name string
}

func main() {
    r1 := Rectangle{2, 1, "my_r1"} //initialize values in order they are defined in struct
    fmt.Println("Rectangle r1 is: ", r1) 

    r2 := Rectangle{width:3, name:"my_r2", length:4} //initialize values by variable name in any order
    fmt.Println("Rectangle r2 is: ", r2) 

    pr := new (Rectangle) //get pointer to an instance with new keyword
    (*pr).width = 6 //set value using . notation by dereferencing pointer.  
    pr.length = 8 //set value using . notation - same as previous.  There is no -> operator like in c++. Go automatically converts
    pr.name = "ptr_to_rectangle"
    fmt.Println("Rectangle pr as address is: ", pr) //Go performs default printing of structs
    fmt.Println("Rectangle pr as value is: ", *pr) //address and value are differentiated with an & symbol
}

Rectangle r1 is: {2 1 my_r1}
Rectangle r2 is: {4 3 my_r2}
Rectangle pr as address is: &{8 6 ptr_to_rectangle}
Rectangle pr as value is: {8 6 ptr_to_rectangle}

The few things to notice here are:
* you can initialize the values within the struct by mentioning it within curly braces in the order in which they appear, each one separated by commas. r1 := Rectangle{2, 1, "my_r1"}
* you can initialize values by giving the name of the variable and its value separated by a colon. r2 := Rectangle{width:3, name:"my_r2", length:4}
* you can get a pointer to a newly created struct instance using the new keyword.
* a pointer thus obtained, can be used with or without using the * operator to get variables within it
* Go provides default print mechanisms for structs based on its values.

Encapsulation and visibility of structs and variables

Other programming languages use a few keywords like public, private, package, protected, etc. to allow the developer to define the visibility and accessibility of variables within different contexts. I thought all of that was absolutely necessary until I saw Go’s approach to it. Go’s approach to variable visibility and accessibility is so simple that you’ll think it is pretty silly. So, without further ado, here it is: if the first letter is capital, it is visible outside the package. That’s it.

Partial code
type notExported struct { //this struct is visible only in this package as it starts with small letter
}

type Exported struct { //variable starts with capital letter, so visible outside this package
    notExportedVariable int //variable starts with small letter, so NOT visible outside package
    ExportedVariable int //variable starts with capital letter, so visible outside package
    s string //not exported
    S string //exported
}   

How is this nothing short of silly you ask? And I say it is nothing short of genius. A simple idea that works so well. For one, all those unwanted extra keywords are eliminated. The second important outcome is that just by looking at the variable you know of its accessibility; you won’t have to scroll back to the definition to find out its visibility. Other languages have provided this as a guideline in their variable naming conventions, but Go enforces it and makes it work very well.


15 comments:

  1. Replace public void getHouseName() with public String getHouseName() to make it similar to Go example in Partial code: In Java

    ReplyDelete
  2. Yep, that is slightly better. Updated.

    ReplyDelete
  3. I ask what mean NOT visible outside the package
    when you private variable in class
    ex
    class name {
    private var;
    }
    this mean it's only scope in this class
    are NOT visible outside the package meaning only inside this struct or what ?

    ReplyDelete
    Replies
    1. it means that functions, identifiers and structs that start with lower case letter they not visible in other packages besides the one they are declared. package can be in few files. so if identifier starts with capital letter its visible in whole package.

      Delete
    2. its sorta like default in java

      Delete
  4. I still don't get why not 'this' syntactic sugar:

    type House struct {

    func GetHouseName() string {

    this.foo

    *this = new (House) //NOT ! :)

    }

    }


    or even a new one:

    type House object {
    ///for a new syntactic sugar using this
    }

    ReplyDelete
  5. Hey, I just want to say thanks a lot for the fantastic tutorials. I've just started getting into go and your blog provides,hands down, the best beginner introduction.

    ReplyDelete
  6. It's a little inaccurate to say that Go is a procedural language. The absence of the `class` keyword and type inheritance doesn't really make it non-OO. http://golang.org/doc/faq#Is_Go_an_object-oriented_language

    ReplyDelete
    Replies
    1. Yes! I had this misconception at the beginning, and I can't quite believe I let this page be for so long. Will fix it. Thank you.

      Delete
  7. I was having a lot of trouble with exporting struct fields. I just didn't have them capitalized. Why was this so hard to find an answer for on the internet? Thank you.

    ReplyDelete
  8. Hi. Please explain why go prints "Rectangle pr as address is: &{8 6 ptr_to_rectangle}" instead of real address?

    ReplyDelete
    Replies
    1. Try the code at http://play.golang.org/p/pDEcovStOn.

      Maybe it's because you didn't use %p in your printf.

      Delete
  9. Mr. Sathish,
    Just a correction - above you mentioned
    " Languages like Java, C#, C++ and others also have structs. "
    which is incorrect as Java doesn't support structures. I know it may not be relevant to topic but it may misdirect others

    ReplyDelete
    Replies
    1. Java has structures but as final classes

      Delete
  10. how to define static values for using in & editing from another packages

    ReplyDelete

If you think others also will find these tutorials useful, kindly "+1" it above and mention the link in your own blogs, responses, and entries on the net so that others also may reach here. Thank you.

Note: Only a member of this blog may post a comment.