From Python to Go - gotchas!

It's been quite a while since I've written a post. And a lot of things have happened in the interim. I've been a Python fanboy for quite a while now, but of late, I've been looking at other languages too. A couple of languages that have particularly caught my fancy are Go and Rust. I won't delve too much into how and why, since that would be the topic of a separate blogpost in the near future. This post is intended to list out a few gotchas to someone who's beginning to program in Go, coming from a language such as Python.

1. (i := 1) != (i = 1)

Okay, that would never compile. This is one of the things that tripped me over and over again as I was trying to write a simple while loop. But since Go only has a for loop, I had to somehow refactor my while logic into a for loop. I thought, "How difficult could it be?". Turns out, it kinda was. But only because I was looking at Go variables as I'd always looked at them in Python.

The following Go code would run into an infinite loop:

// Go code
package main  
import "fmt"

func main() {

    for i := 0; i < 100; {
        fmt.Println(i)
        i := i + 2
        if i > 100 {
            break
        }
    }
}

"Why?", you ask? Let's look at the equivalent code in Python:

# Python code
i = 0  
while i < 100:  
    print(i)
    i = i + 2

The code in Python would run and quit after i reaches 98.

The following snippet should probably help in understanding how Go treats variables.

/* 
Go program that prints the memory addresses of variables  
*/
package main  
import "fmt"

func main() {  
    a := 5
    fmt.Println(&a)
    a = 6
    fmt.Println(&a)
}

The output would be

0x104382e0  
0x104382e0  

You could try it out here https://play.golang.org/p/W4QcWeDpam.

Let's see what happens if we re-instantiate the value:

// Go code
package main  
import "fmt"

func main() {  
    a := 5
    fmt.Println(&a)
    a = 6
    fmt.Println(&a)
    a := 7
    fmt.Println(&a)
}
prog.go:11: no new variables on left side of :=

Program exited.  

Run the code here https://play.golang.org/p/wulKulOhqS.

Let's look at the memory addresses of variables in Python

>>># Python program that displays memory addresses of variables
>>>a = 5
>>>id(a)
4297537984  
>>>a = 7
>>>id(a)
4297538016  
>>># In case you didn't notice, the memory addresses are different.

Now coming to the actual point of why Go would run into an infinite loop - I didn't know Go supported 2 operators for assignment --> := and =. := is for assignment, and = is reassignment, in case the value of the variable needs to be modified, but the reference needs to be preserved, then = is to be used.

This is what Python does implicitly. And AFAIK, JavaScript, C, C++, Java, etc. follow the same approach. And since a new variable was being defined (Go program), the compiler raised an error that the variable wasn't being used anywhere else, and hence couldn't compile. I'd love to know how other languages still maintain reference to the original variable, when it has been reassigned. But looking at the approach taken by Go, it seems pretty straightforward. Store all the values in the same memory region. And that obviously works. And when the programmer does something fishy, by reassigning the value, then raise a compilation error.

So the correct way of running the program in Go would be:

package main  
import "fmt"

func main() {  
    i := 0
    for {
        fmt.Println(i)
        i = i + 2
        if i > 100 {
            break
        }
    }
}

I'm guessing this blog post would surely host many such gotchas. Go seems vastly different from Python - and in a good way, I guess. I never gave a passing thought to the work that a garbage collector would need to perform to constantly remove references to variables when I was reassigning them. In Go, I'm forced to reuse the variable. This, I feel reduces the workload on the GC, and hence improves the performance of the program, and consequently, helps me become a better programmer.