Gotchas in Golang


Golang Gotchas

Writing programs that work when everything goes as expected is a good start. Making your programs behave properly when encountering unexpected conditions is where it really gets challenging.

Even seasoned Gopher can get trapped in one of the many pitfalls of the Go language. But there are some cases which might confuse or rather trick a rookie coder. These are called "Gotchas"! Originating from the informal term "Got You!", a gotcha is a case or scenario when the program plays the trick and results in an output that is quite different from what was expected.

The list of common gotchas is far from exhaustive, and is presented in no particular order of severity.


Mismatched types int and float64

The Go type system will not allow any mathematical operation between integer and float variables.

Fails

package main

import "fmt"

func main() {
	var x, y = 13, 3.5
	fmt.Println(x / y)
}

Error

.\error-1.go:7:16: invalid operation: x / y (mismatched types int and float64)
What do you think will be the output of the following program?

Works

package main

import "fmt"

func main() {
	var x = 13 / 3.5
	fmt.Println(x)
}

In the above program, the right side of = are constants, not variables. Hence compiler will convert 13 into a float type to execute the program. This program will print the output −

Output

3.7142857142857144
Solution

Example

package main

import "fmt"

func main() {
	var x, y = 13, 3.5
	fmt.Println(float64(x) / y)
}

Use float64 to convert type of x into float64.


Assignment to entry in nil map

Map types are reference types, like pointers or slices, and so the value of rect is nil; it doesn't point to an initialized map. A nil map behaves like an empty map when reading, but attempts to write to a nil map will cause a runtime panic; don't do that.

Fails

package main

import "fmt"

func main() {
	var rect map[string]int
	rect["height"] = 10
	fmt.Println(rect["height"])
}

Error

panic: assignment to entry in nil map
What do you think will be the output of the following program?

Works

package main

import "fmt"

func main() {
	var rect map[string]int
	fmt.Println(rect["height"])
	fmt.Println(len(rect))

	idx, key := rect["height"]
	fmt.Println(idx)
	fmt.Println(key)
}

The Zero Value of an uninitialized map is nil. Both len and accessing the value of rect["height"] will work on nil map. len returns 0 and the key of "height" is not found in map and you will get back zero value for int which is 0. Similarly, idx will return 0 and key will return false.

Output

0
0
0
false
Solution

Works

package main

import "fmt"

func main() {
	var rect = map[string]int{"height": 10}
	fmt.Println(rect["height"])
}

You can also make a map and set its initial value with curly brackets {}.


Raw string literals vs Interpreted string literals

There are two different ways to represent string literals.

Raw String

Example

package main

import "fmt"

func main() {
	s := `Go\tJava\nPython`
	fmt.Println(s)
}
What do you think will be the output of the above program?

Output

Go\tJava\nPython

Raw strings are enclosed in back-ticks `. Here, \t and \n has no special meaning, they are considered as backslash with t and backslash with n. If you need to include backslashes, double quotes or newlines in your string, use a raw string literal.

Interpreted String

Example

package main

import "fmt"

func main() {
	s := "Go\tJava\nPython"
	fmt.Println(s)
}

Raw strings are enclosed in quotes ". Hence \t would be interpreted as tab and \n as new line. The above program will print,

Output

Go      Java
Python

Invalid operation mismatched types Int and Time.Duration

The operands to numeric operations must have the same type unless the operation involves shifts or untyped constants.

Fails

package main

import (
	"fmt"
	"time"
)

func main() {
	var timeout = 3
	fmt.Println(timeout)
	fmt.Println(timeout * time.Millisecond)
}

Error

.\error-1.go:11:22: invalid operation: timeout * time.Millisecond (mismatched types int and time.Duration)
What do you think will be the output of the following program?

Works

package main

import (
	"fmt"
	"time"
)

func main() {
	const timeout = 10
	fmt.Println(timeout)
	fmt.Println(timeout * time.Millisecond)
}

Millisecond's underlying type is an int64, which the compiler knows how to convert to. Literals and constants are untyped until they are used, unless the type is explicitly declared. timeout is an untyped constant in this example. Its type is implicitly converted to time.Millisecond. This program will print the output −

Output

10
10ms
Solution

Example

package main

import (
	"fmt"
	"time"
)

func main() {
	var timeout time.Duration
	timeout = 10
	fmt.Println(timeout * time.Millisecond)
}

Define the type of timeout as time.Duration.


String Length Bytes vs Runes

When you ask the length of string in Go, you will get the size in bytes.

Number of Bytes

Example

package main

import "fmt"

func main() {
	data := "We♥Go"
	fmt.Println(len(data))
}
What do you think will be the output of the above program?

Output

7

If you count the number of charcters is "We♥Go", it would be 5. So why 7?

In Go Strings are UTF-8 encoded, this means each charcter called rune can be of 1 to 4 bytes long. Here,the charcter ♥ is taking 3 bytes, hence the total length of string is 7.

Number of Runes

Example

package main

import (
	"fmt"
	"unicode/utf8"
)

func main() {
	data := "We♥Go"
	fmt.Println(utf8.RuneCountInString(data))
}

If you want to get the number of runes in the string, you can use the unicode/utf8 package. The RuneCountInString function will return number of runes in a string. The above program will print,

Output

5

Initialize variable with nil

Nil is not a type but a reserved word, you cannot use it in assignment.

Fails

package main

import (
	"fmt"
)

func main() {
	var data string = nil
	if data == nil {
		fmt.Println(data)
	}
}

Output

cannot use nil as type string in assignment
What do you think will be the output of the following program?

Works

package main

import (
	"fmt"
)

func main() {
	var data *string = nil
	if data == nil {
		fmt.Println(data)
	}
}

*string is the type of the pointer variable which points to a value of type string. The zero value of a pointer is nil.

Output

nil

Floating point multiplication

Floating-point arithmetic is considered an esoteric subject by many people.

Example

package main

import (
	"fmt"
)

func main() {
	 var m = 1.39
	 fmt.Println(m * m)
	 
	 const n = 1.39
	 fmt.Println(n * n)
}
What do you think will be the output of the following program?

Output

1.9320999999999997
1.9321

This is rather surprising because floating-point is ubiquitous in computer systems. Almost every language has a floating-point datatype; computers from PCs to supercomputers have floating-point accelerators; most compilers will be called upon to compile floating-point algorithms from time to time; and virtually every operating system must respond to floating-point exceptions such as overflow.

T is a floating-point type and n can be rounded to T's precision without overflow. Rounding uses IEEE 754 round-to-even rules but with an IEEE negative zero further simplified to an unsigned zero. Note that constant values never result in an IEEE negative zero, NaN, or infinity.


String type conversion

Go doesn't allow automatic type promotion between variables. You must use a type conversion when variable types do not match.

Fails

package main

import "fmt"

func main() {
	 i := 105
	 s := string(i)
	 fmt.Println(s)	 
}
What do you think will be the output of the above program?

Output

i

The string supports type conversion from int, here string() will treat integer as rune. The rune of 105 is i.

Integer to String

Works

package main

import (
	"fmt"
	"strconv"
)

func main() {
	i := 105
	s := strconv.Itoa(i)
	fmt.Println(s)
	
	s = fmt.Sprintf("%d", i)
	fmt.Println(s)
}

To convert an integer variable into String use any either strconv.Itoa() or fmt.Sprintf() function.

Output

105
105

Unused Variables vs Unused Constants

Go has some rules that are unique among programming languages.

Unused Variables

Example

package main

func main() {
	var i = 100
}

Output

i declared and not used

If you have unused variables, the code will not compile. If you assign a value to an unused variable, still your code will not compile. We'll have to use it somewhere to please the compiler.

Unused Constants

Example

package main

func main() {
	const i = 100
}

The above program won't print any exception or error. This is because constants in Go are calculated at compile time and cannot have any side-effects. This makes them easy to eliminate and they are not included in the compiled binary.