Learn Go · Part 2 of 11

Constants, Variables and Basic Data Types

Making variables, all the number types and what to watch out for with them, strings, zero values and constants.

Video 6:25–13:14 · 7 topics · 14 exercises

All parts · this is part 2
  1. 1.Introduction to Go
  2. 2.Constants, Variables and Basic Data Types
  3. 3.Functions and Control Structures
  4. 4.Arrays, Slices, Maps and Loops
  5. 5.Strings, Runes and Bytes
  6. 6.Structs and Interfaces
  7. 7.Pointers
  8. 8.Goroutines
  9. 9.Channels
  10. 10.Generics
  11. 11.Building an API

Extras

This part starts at 6:25 in the video.

Declaring variables

To make a variable you use the var keyword, then the name, then the type. But there are a few shortcuts too:

main.gogo
package main

import "fmt"

func main() {
	var intNum int             // declared, set to int's zero value: 0
	var name string = "Gopher" // type and value
	var count = 10             // type inferred: int
	city := "Toronto"          // short form: inferred, no var

	var a, b int = 1, 2 // several at once
	x, y := "x", 3.5    // they can even be different types

	fmt.Println(intNum, name, count, city, a, b, x, y)
}

Notice I'm printing everything at the end. That's because, just like imports, if you declare a variable and never use it, Go won't compile. It's part of Go keeping things simple: no pointless variables hanging around in your code.

A couple of gotchas with :=:

main.gogo
package main

import "fmt"

var version = "1.0" // fine: var works outside functions
// build := 42      // won't compile: := only works inside a function

func main() {
	n := 1
	n, m := 2, 3 // fine: m is new, so := is allowed, and n is just reassigned
	fmt.Println(version, n, m)
}

Practice

  • PracticeWhen does := work?

    This program uses := outside a function, and twice in a row on the same name, so it won't compile. Fix both, and say what each error means.

    Practice with AI
  • Warm-upFour ways to make 42

    Make an int that holds 42 in four different ways and print them all on one line.

    Practice with AI

Integers and their sizes

Go doesn't just have one integer type, it has a bunch, and the difference is how much memory they use. Bigger types can hold bigger numbers but take up more space. There are also unsigned ones (uint) that can only be positive, which means they can go twice as high with the same memory.

TypeBitsRange
int88-128 to 127
int1616-32,768 to 32,767
int3232about ±2.1 billion
int6464about ±9.2 quintillion
uint8 (byte)80 to 255
uint16 / uint32 / uint6416 / 32 / 640 up to about twice the signed version's max
int / uint32 or 64Depends on your machine, which these days is pretty much always 64

Now here's where it gets fun. If you try to put a number that's too big into a type, like 32,768 into an int16, the compiler catches it. But only if it can see it. If the number gets too big while your program is running, Go doesn't throw an error. It just wraps around and keeps going, and now you've got a weird number and no idea where it came from.

main.gogo
package main

import "fmt"

func main() {
	// var big int16 = 32768 // compile error: 32768 overflows int16

	var n int16 = 32767
	n = n + 1
	fmt.Println(n) // -32768: no error, it just wraps around

	var u uint8 = 0
	u--
	fmt.Println(u) // 255
}

Moral of the story: think about what values you might actually run into and pick types that fit. It'll save you some late night debugging sessions.

Practice

  • PracticeWatch it wrap

    An int8 starts at 120 and has 1 added to it ten times, printing each time. Guess what it prints, then run it. Why didn't the compiler catch it?

    Practice with AI
  • StretchAdd without overflowing

    Write addInt8(a, b int8) (int8, bool) that gives back false instead of a wrapped number when the answer doesn't fit.

    Practice with AI

Floating-point numbers

For decimal numbers you've got float32 and float64. There's no plain float, you have to pick one. float64 holds bigger and more precise numbers, and it's what you get by default if you write something like x := 2.5.

Thing is, most decimals can't be stored exactly on a computer, so you get the closest number the type can hold. With 32 bits that's often noticeably off:

main.gogo
package main

import "fmt"

func main() {
	var f32 float32 = 12345678.9
	var f64 float64 = 12345678.9
	fmt.Println(f32) // 1.2345679e+07
	fmt.Println(f64) // 1.23456789e+07

	fmt.Println(0.1+0.2 == 0.3) // true: these are constants, worked out exactly

	a, b := 0.1, 0.2
	fmt.Println(a+b == 0.3) // false: a+b is 0.30000000000000004
}

That last one is the one that'll get you. a + b is actually 0.30000000000000004, so it's not equal to 0.3. If you're comparing floats after doing math on them, don't use ==. Check if they're close enough instead:

main.gogo
package main

import (
	"fmt"
	"math"
)

func almostEqual(a, b float64) bool {
	return math.Abs(a-b) < 1e-9
}

func main() {
	a, b := 0.1, 0.2
	fmt.Println(almostEqual(a+b, 0.3)) // true
}

Practice

  • PracticeTen cents at a time

    Add 0.1 to a float64 ten times and compare it to 1.0. Then do the same thing in cents with an int64. Which one gets it right?

    Practice with AI

Arithmetic and type conversion

You can add, subtract, multiply and all that stuff like you'd expect, but there's a catch: you can't mix types. You can't add an int and a float32, and you can't even multiply an int32 by an int64. If you need to, you convert one of them, which looks like calling the type as a function:

main.gogo
package main

import "fmt"

func main() {
	var count int = 3
	var price float32 = 2.5

	// total := count * price      // won't compile: int and float32
	total := float32(count) * price // convert one side
	fmt.Println(total)              // 7.5

	fmt.Println(int(3.9 * float64(count))) // 11: converting to int drops the decimals

	fmt.Println(3 / 2)   // 1
	fmt.Println(-3 / 2)  // -1: rounds toward zero, not down
	fmt.Println(7 % 3)   // 1
	fmt.Println(-7 % 3)  // -1: the remainder takes the sign of the left side
	fmt.Println(3.0 / 2) // 1.5: a float on either side gives float division
}

A few things worth pointing out in there:

  • Integer division gives you an integer. 3 / 2 is 1, and the decimal just gets chopped off. Careful with negatives though: -3 / 2 is -1, not -2, because Go rounds toward zero.
  • % gives you the remainder, and it comes out negative if the left side is negative. So if you're checking whether something's odd, use n%2 != 0. n%2 == 1 will lie to you for negative numbers.
  • Converting a float to an int doesn't round, it just drops everything after the decimal.

There are also shorthands like x += 2, x -= 2, x *= 2 and x /= 2, plus x++ and x-- to add or subtract one. One weird thing: in Go, x++ is its own statement, so something like y := x++ won't work.

Practice

  • Warm-upAverage of three

    Fill in average(a, b, c int) float64 so it gives back the average of three int scores. 1, 2 and 2 should give you 1.666…, not 1.

    Practice with AI
  • PracticeMinutes and seconds

    Fill in split(total int) (minutes, seconds int) so 135 gives back 2 and 15, using / and %.

    Practice with AI
  • StretchOdd numbers, negatives too

    Write isOdd(n int) bool that works for -3, 0 and 7. Why doesn't n%2 == 1 work?

    Practice with AI

Strings, at first glance

This might blow your mind, but you use the string type to store strings. You can make one with double quotes or back quotes. Double quotes have to stay on one line, so if you want a line break you use \n. Back quotes print exactly what you type, line breaks and all, which is super handy for stuff like JSON or SQL.

main.gogo
package main

import (
	"fmt"
	"unicode/utf8"
)

func main() {
	greeting := "Hello\nWorld" // \n is a newline
	raw := `Hello
World` // back quotes: printed exactly as typed, \n and all

	fmt.Println(greeting)
	fmt.Println(raw)
	fmt.Println("Hello" + " " + "Gopher") // + joins strings

	fmt.Println(len("A"))                    // 1
	fmt.Println(len("γ"))                    // 2: len counts bytes, and γ takes two
	fmt.Println(utf8.RuneCountInString("γ")) // 1: counts characters

	var r rune = 'a' // single quotes make a rune: one character
	fmt.Println(r)   // 97: a rune is a number
}

Now here's where strings get a little tricky. len doesn't give you the number of characters, it gives you the number of bytes. Go stores strings as UTF-8, where normal English letters take one byte but stuff like accents, Greek letters or emoji take two to four. So if you actually want the number of characters, use utf8.RuneCountInString.

Practice

  • Warm-upTwo kinds of quotes

    Print a two-line poem with double quotes and \n, then again with back quotes. Make them come out exactly the same.

    Practice with AI
  • PracticeCount the characters

    Guess len and the rune count for "hello", "héllo" and "日本", then run it. Why are the numbers different?

    Practice with AI

Booleans and zero values

Booleans are easy. They're true or false. Congrats, you now understand booleans in Go.

One thing if you're coming from Python or JavaScript: Go doesn't treat 0 or "" as false. So if count { } won't compile. You have to write if count != 0.

Also, so far we've mostly set variables when we made them, but you don't have to. If you don't, Go gives the variable a default value based on its type, called its zero value:

main.gogo
package main

import "fmt"

func main() {
	var i int
	var f float64
	var s string
	var b bool
	var r rune

	fmt.Printf("%v %v %q %v %v\n", i, f, s, b, r) // 0 0 "" false 0
}
TypeZero value
Integers, floats, runes0
string"" (the empty string)
boolfalse
Pointers, slices, maps, errorsnil (parts 3, 4 and 7)

This actually comes in handy a lot. A counter starts at 0 without you doing anything, and structs (part 6) are often ready to use as soon as you declare them.

Practice

  • Warm-upGuess the zero value

    Declare a uint8, float32, string, bool and rune without giving them values. Guess what each one prints, then check.

    Practice with AI
  • PracticeNo truthy values here

    Fix if name { … } and if !count { … } so they compile but still do what the person meant.

    Practice with AI

Constants

Constants are like variables, except once you set them you can't change them. Everything else we talked about still applies, with one catch: you can't just declare a constant, you have to give it a value right away.

main.gogo
package main

import "fmt"

const pi = 3.14159

// const later int // won't compile: a constant needs its value right away

func main() {
	const radius = 2
	area := pi * radius * radius // radius is untyped, so it works with pi
	fmt.Println(area)            // 12.56636

	// pi = 3 // won't compile: you can't assign to a constant
}

They're great for values nobody should be messing with later. Take pi. There's no reason for anyone to change it, and if someone did, every calculation using it would be off.

Something you might notice: pi * radius works even though pi looks like a float and radius looks like an int. That's because constants without a type written out are untyped, so they just become whatever type they need to be. Two variables with those same values wouldn't multiply.

One more trick. If you need a bunch of related numbers in a row, like days of the week, Go has iota. In a const block it starts at 0 and goes up by one each line, and if you leave a line without a value it reuses the one above:

main.gogo
package main

import "fmt"

type Weekday int

const (
	Sunday  Weekday = iota // 0
	Monday                 // 1
	Tuesday                // 2
)

const (
	KB = 1 << (10 * (iota + 1)) // 1024
	MB                          // 1048576
	GB                          // 1073741824
)

func main() {
	fmt.Println(Sunday, Monday, Tuesday)
	fmt.Println(KB, MB, GB)
}

Practice

  • PracticeCircle math

    The template has const pi and area(radius int) float64. pi * radius * radius compiled when radius was a constant, but radius is a variable now and it doesn't, even though pi is still a constant. Explain the error, then fix it.

    Practice with AI
  • PracticeSizes with iota

    Use iota to make Small, Medium and Large. Then use iota for KB, MB and GB constants, and give back how many bytes are in 3 MB.

    Practice with AI