Go: Basic Types

These are just some notes I’ve taken from The Go Programming Language by Alan A. A. Donovan & Brian W. Kernighan (which I strongly recommend if you are willing to learn Go).

These notes and code samples are mainly for self-reference. Don’t expect anything new or exciting here 🙄

Go’s types can be grouped in 4 categories:

  • basic types
  • aggregate types
  • reference types
  • interface types

This post focus on basic types.

Basic types

Basic data types can be numbers, strings or booleans.

Numbers

Go has 3 types of numbers: integers, floating-points and complex numbers.

Signed numbers are represented in 2’s-complement form.

Integers

The zero value for type int is… 0.

Go provides signed (i.e. int8, int16, int32, and int64) and unsigned (i.e. uint8, uint16, uint32, and uint64) integers.

int and uint are the most efficient size for integers on a given platform. They have the same size, either 32 or 64 bits. Different compilers can make different choices on whether so use one or the other.

rune is a synonym for int32 and indicates that a value is a Unicode code point. Runes are printed with verbs %c or %q:

a := 'a'
fmt.Printf("%d %[1]c %[1]q\n", a) // "97 a 'a'"

byte is a synonym for uint8. It usually contains a piece of raw data.

uintptr is an unsigned integer type used to represent pointer values. It’s size is not specified. It is generally used for low-level programming (i.e. when interoperating with C).

int, int32, uint, uintptr, and so forth, are different types and require explicit type conversion when used in a same operation:

var _1 int32 = 1
var _2 int16 = 2
var sum int
sum = _1 + _2 // compile error: mismatched types int32 and int16
sum = int(_1) + int(_2) // works fine

When an overflow happens (the result of an arithmetic operation has more bits than its result type can hold), the high-order bits that do not fit are silently dropped.

Float to integer conversion discards any fractional part, truncating toward zero.

Integer literals can be also written as octal numbers if they start with 0, like in 0444, or as hexadecimal if they begin with 0x (or 0X), as in 0xcafe.

By using the fmt package, we can control the radix and use the verbs %d, %o, and %x to format:

o := 0444
fmt.Printf("%d %[1]o %#[1]o\n", o) // "292 444 0444"
x := int64(0xcafe)
fmt.Printf("%d %[1]x %#[1]x %#[1]X\n", x) // "51966 cafe 0xcafe 0XCAFE"

Floating-Point Numbers

Go provides two sizes of floating-points, float32 and float64. The limits of floating-point values can be found in the math package.

A float32 provides about 6 decimal digits of precision. A float64 provides about 15 digits.

Use scientific notation to write very small or large numbers, with the letter e or E preceding the decimal exponent:

const Avogadro = 6.02214129e23
const Planck = 6.62606957e-34

Floating-point values are usually printed with %g verb. But it is also possible to use %e (exponent) or %f (no exponent) verbs.

const Avogadro = 6.02214129e23
fmt.Printf("%g %8.3[1]f %[1]e\n", Avogadro) // 6.02214129e+23 602214128999999968641024.000 6.022141e+23

Testing whether a specific result is equal to math.NaN (not a number) is dangerous because comparisons with NaN always yields false.

Complex Numbers

Go provides two sizes of complex numbers, complex64 and complex128. The built-in function complex creates a complex number from its real and imaginary components. The built-in real and imag functions return the real and imaginary components, respectively:

var x complex128 = complex(1, 2) // 1+2i
var y complex128 = complex(3, 4) // 3+4i
fmt.Println(x*y) // "(-5+10i)"
fmt.Println(real(x*y)) // "-5"
fmt.Println(imag(x*y)) // "10"

Floating-point literals or decimal integer literals immediately followed by i, as in 3.14i or 1i, denote a complex number with a zero real component.

The math/cmplx package provides functions for working with complex numbers.

Booleans

Two possible values: true and false. The zero value for boolean types is false.

Use the verb %t to print booleans:

fmt.Printf("%t", false) // "false"
fmt.Printf("%t", !false) // "true"

Strings

In Go strings are immutable sequences of bytes. By convention, text strings are interpreted as UTF-8 encoded sequences of Unicode code points (runes). To know more about Unicode/UTF-8, I recommend this excelent blog post from Joel Spolsky.

A string literal is a sequence of bytes inside double quotes, such as "hello world". The zero value for string types is the empty string "".

Raw string literals are represented like `hello world`.  In a raw string literal, no escape sequences are processed. Raw string literals are a generally used to write regular expressions, HTML, JSON or YAML.

The built-in len function returns the number of bytes (not runes) in a string, and the index operation myString[i] retrieves the i-th byte of myString.

The i-th byte of a string is not necessarily the i-th character of a string, because the UTF-8 encoding of a Unicode code point can require two or more bytes.

The unicode package provides functions for working with runes, and the unicode/utf8 package provides functions for encoding and decoding runes as bytes using UTF-8.

It is possible to specify characters in a string using Unicode escapes followed by their numeric code points (h is a hexadecimal digit):

  • \uhhhh for a 16-bit value
  • \Uhhhhhhhh for a 32-bit value
fmt.Println("\xe4\xb8\x96\xe7\x95\x8c") // 世界
fmt.Println("\u4e16\u754c") // 世界
fmt.Println("\U00004e16\U0000754c") // 世界

The substring operation s[i:j] returns a new string based on the original one starting at index i until the byte at index j (j is not included). i and/or j operands can be omitted.

s := "hello world"
fmt.Println(s[:5]) // "hello"
fmt.Println(s[7:]) // "world"
fmt.Println(s[:]) // "hello, world"

The + operator makes a new string by concatenating two strings.