STOP: Each chapter of Programming for Lovers comprises two parts. First, the “core text” presents critical concepts at a high level, avoiding language-specific details. The core text is followed by “code alongs,” where you will apply what you have learned while learning the specifics of the language syntax. We strongly suggest starting with this chapter’s core text (click the button below) to grasp the fundamentals before moving onto code alongs to see the application of these fundamentals. Once you have done so, you are ready to proceed with this code along!

Learning objectives

Now that we have written our first program, we are ready to start seeing how Go implements variables, functions, if statements, loops, and other topics that we introduced on the level of pseudocode in the prologue. Once we have learned these fundamentals, we will be ready to implement Euclid’s GCD algorithm and Eratosthenes’ prime finding algorithm and then time these two algorithms to see if they’re really as fast as we claimed.

In our first lesson on fundamentals, we will introduce types and numeric variables in Go.

Setup

Create a folder called variables in your go/src directory and create a text file called main.go in the go/src/variables folder. We will edit main.go in this lesson.

Code along video

Note: We call these lessons “code alongs” for a reason, which is that we strongly suggest that you not just watch the video but that you type out the code in your text editor and run your code with us.

Beneath the video, we provide a detailed summary of the topics and code covered in the code along.

Although we strongly suggest completing the code along on your own, you can find completed code from the code along in our course code repository.

Code along summary

Getting started

We are going to write code that we will execute in a program, and so we include the following declaration at the top of main.go to indicate this to Go.

package main
Note: At the top right of each code block, you will see icons allowing you to copy the code in the code block as well as download a text file containing the code block. Please use these how you like, although we suggest typing them yourself!

Function comments

First, we introduce comments. Comments are “notes” to ourselves or someone else running the code that can be helpful. Typically beginner programmers don’t initially realize how valuable comments can be for their own learning; comment your code frequently!

In Go, anything after a double slash (//) is not read. A text editor will often automatically color a comment gray. Let’s type our first comment.

package main

// Go won't read this line

Sometimes, we want a comment to span multiple lines. In Go, a multi-line comment begins with /* and ends with */.

package main

// Go won't read this line

/*
Everything here won't be read either.
This is a multi-line comment.
*/

Creating a runnable program

Let’s first create a runnable program. Much like in our first program (from the preceding lesson) we will need to add an import of the "fmt" package so that we can print messages to the console. Let’s do so and then add a print statement to the console in func main().

package main 

import (
    "fmt"
)

// Go won't read this line

/*
Everything here won't be read either.
This is a multi-line comment.
*/

func main() {
    fmt.Println("Let's get started!")
}
STOP: Let’s run this code. Open a command line terminal and navigate into the folder containing your code by using the command cd go/src/intro. Then, compile your code by executing the command go build. You can then run the code by executing the command intro.exe on Windows or ./intro on Mac. You should see Let's get started! printed to the console.

Additional white space does not matter

Before continuing, we pause to note that additional blank lines in our code does not affect its output. The above program, for example, is equivalent to the one below, in which we have added lines of whitespace.

package main 


import (
    "fmt"
)


// Go won't read this line


/*
Everything here won't be read either.
This is a multi-line comment.
*/


func main() {

    fmt.Println("Let's get started!")

}
Note: Although the code above has too much whitespace, beginners tend to cram their code together line after line without many comments. Don’t hesitate to provide lots of comments on your code and give it space to breathe! These stylistic points will make your code easier for you, and others, to work with.

Six common variable types

We will introduce four variable types. Each type represents a different category of variable.

  1. int: an integer, which can be positive, negative, or zero;
  2. uint: a nonnegative integer (the “u” stands for “unsigned”);
  3. bool: a true or false Boolean variable;
  4. float64: a decimal number.

We will introduce two additional types here that we will work more with in the next chapter when it comes time to analyze DNA sequences.

  1. byte: a single symbol;
  2. string: a contiguous collections of symbols (i.e., a word or text).

Declaring variables

In what follows, we will edit func main(), which starts at line 14 of our main.go file. First, we will declare six variables, one for each of the types introduced above. By declaring a variable, we allocate memory for this variable, and from that point forward, we can refer to the variable using its declared name. In Go, we declare a variable by using the keyword var, followed by the name of the variable, followed by the type of the variable. Here are our six declarations of variables named j, x, yo, u, symbol, and statement; these variables have the respective types int, float64, string, uint, byte, and bool.

func main() {
    fmt.Println("Let's get started!")

    var j int
    var x float64
    var yo string
    var u uint
    var symbol byte
    var statement bool
}
Note: You can name a variable anything you like as long as the name is not already assigned to a variable, and as as long as the name is not a keyword (like var, if, or for).
STOP: Save main.go, and then compile your program in the command line by executing the command go build. You will see a compiler error because although the variables have been declared, they are not used. This is a nice feature of Go, since it prevents you from declaring variables that you forget to use.

To use the variables that we declared, let’s print them to the console and see what values they currently have. After adding the code below and running go build in the terminal again, we get no feedback, which means that the compiler found no issues with our code and created an executable file in your go/src/intro folder. This executable file is called intro, the name of the parent folder. To run that code, from the command line, execute the command intro.exe on Windows or ./intro on Mac.

func main() {
    fmt.Println("Let's get started!")

    var j int
    var x float64
    var yo string
    var u uint
    var symbol byte
    var statement bool

    fmt.Println(j)
    fmt.Println(x)
    fmt.Println(yo)
    fmt.Println(u)
    fmt.Println(symbol)
    fmt.Println(statement)
}

The value of each variable has been printed on its own line by the fmt.Println() command. Below are the default values of these variables that they are assigned when declared.

  1. int: 0;
  2. uint: 0;
  3. bool: false;
  4. float64: 0;
  5. byte: 0 (this will make sense in time);
  6. string: the “empty string”, which is denoted by "" and when printed displays nothing.
STOP: Part of learning to program in a compiled language is starting to understand what each compiler error means. If we were to declare a variable without specifying its type, or forget to import the "fmt" package, then we would obtain a compiler error when executing go build. Incorporate each of these errors into your code and compile. Note the error messages that Go provides. Each such compiler error is of the form main.go:X:Y:error_message where main.go is the file, X is the line number where the error is encountered, Y is the character number in that line where the error is encountered, and error_message describes the error.

In practice, we typically will want to set the value of a variable when we declare it. In Go, we set a variable at declaration by adding an = sign and the value after the variable declaration. Let’s edit our code above so that each variable that we declared has a value. Note that the string "Hi" is enclosed in double quotation marks but the symbol 'H' is enclosed in single quotation marks.

func main() {
    fmt.Println("Let's get started!")

    var j int = 14
    var x float64 = -2.3
    var yo string = "Hi"
    var u uint = 14
    var symbol byte = 'H'
    var statement bool = true

    fmt.Println(j)
    fmt.Println(x)
    fmt.Println(yo)
    fmt.Println(u)
    fmt.Println(symbol)
    fmt.Println(statement)
}

This time, when we compile and run the code, we see all the values that we set get printed to the console, with one weirdness. When printing symbol, 72 is printed! The reason why is that Go is thinking of characters as corresponding to a table, in which each character is assigned its own integer. We will explain more about this later when we discuss symbols in greater depth.

Shorthand declarations

Let’s clean up main.go by deleting what is presently in func main(), along with the comments at the start of the file.

In practice, rather than using the lengthy notation var variableName type = value, we will often use short variable declarations of the form variableName := value.

For example, the following code shows the short declaration of five variables. Any integer declared with a short declaration, whether positive or negative, will have type int by default. The following code also shows that we can pass multiple inputs to fmt.Println(). When we compile and run this code, each variable’s value will be printed, separated by a space symbol each time.

func main() {
    // shorthand variable declarations
    i := -6 // automatically has type int (we must declare a uint manually)
    hi := "Yo " // automatically has type string
    k := 34 // automatically has type int
    y := 3.7 // automatically has type float64
    secondStatement := true // automatically has type bool

    //we can print multiple values at once on a single line 
    fmt.Println(i, hi, k, y, secondStatement)
}

Arithmetic on numeric variables

Once we declare numeric variables (of type int, uint, or float64), we can then perform arithmetic on them, following the standard order of operations. For example, the following two new lines will print the values represented by the expressions 2*(i+5)*k and 2*y - 3.16, which are equal to 68 and 4.24, respectively.

func main() {
    // shorthand variable declarations
    i := -6 // automatically has type int (if you want a uint, declare it)
    hi := "Yo " // automatically has type string
    k := 34 // automatically has type int
    y := 3.7 // automatically has type float64
    secondStatement := true // automatically has type bool

    //we can print multiple values at once on a single line 
    fmt.Println(i, hi, k, y, secondStatement)

    // we can perform arithmetic on numeric variables
    fmt.Println(2*(i+5)*k)
    fmt.Println(2*y - 3.16)
}

A critical point is that Go follows the standard order of operations for arithmetic. For example, in the expression 2*(i+5)*k, the addition i+5 is performed first, followed by the two multiplications.

STOP: How would the second print statement in the above code change if we instead printed 2*i+5*k?
Note: A common programming error is writing an expression like 2(i+5). Although this expression looks perfectly reasonable to a human reader, the compiler is unable to parse it, as it is missing the multiplication symbol; make sure that you remember your multiplication symbols!

If we wanted to multiply k and y together and print the result, as shown below, then we might anticipate that 125.8 would be printed, since it is the product of 34 and 3.7, the values of k and y. However, if you compile and run the following code, then you will obtain a compiler error; Go wants you to perform arithmetic operations on variables of the same type, but k and y have types int and float64, respectively.

func main() {
    // shorthand variable declarations
    i := -6 // automatically has type int (if you want a uint, declare it)
    hi := "Yo " // automatically has type string
    k := 34 // automatically has type int
    y := 3.7 // automatically has type float64
    secondStatement := true // automatically has type bool

    //we can print multiple values at once on a single line 
    fmt.Println(i, hi, k, y, secondStatement)

    // we can perform arithmetic on numeric variables
    fmt.Println(2*(i+5)*k)
    fmt.Println(2*y - 3.16)

    fmt.Println(k*y)
}

We can resolve this compiler error by casting the integer k to a float64 type, which allows us to perform the multiplication without changing the underlying type of k, as shown in the updated line below.

    fmt.Println(float64(k)*y) // we need to cast k to a float64
Note: Depending on your computer, you may also find that what is printed is not 125.8, but rather 125.80000000000001. This type of small round-off error arises due to each variable being assigned a finite amount of space, an issue that we will discuss later in the course.

Integer division

You might also expect that printing k/14 would produce 2.4285714. However, if you compile and run the code below, then you will see that the final line printed is 2. If we divide two integers in Go (whether they are variables or constants), then Go will interpret the division as integer division, meaning that the remainder is discarded. As we will see, integer division is useful in certain contexts.

func main() {
    // shorthand variable declarations
    i := -6 // automatically has type int (if you want a uint, declare it)
    hi := "Yo " // automatically has type string
    k := 34 // automatically has type int
    y := 3.7 // automatically has type float64
    secondStatement := true // automatically has type bool

    //we can print multiple values at once on a single line 
    fmt.Println(i, hi, k, y, secondStatement)

    // we can perform arithmetic on numeric variables
    fmt.Println(2*(i+5)*k)
    fmt.Println(2*y - 3.16)

    fmt.Println(float64(k)*y) // we need to cast k to a float64

    fmt.Println(k/14) // integer division
}

In this case, if we would like for the division to be performed as decimal numbers, then we should cast k to type float64.

    fmt.Println(k/14) // integer division
    fmt.Println(float64(k)/14.0) // prints 2.4285714
Note: Not every type conversion will work. For example, in some languages, bool(0) would evaluate to false. In Go, however, this will result in a compiler error.

Integer overflow and underflow

STOP: Consider the following func main(). What do you think will be printed when we run this code?
func main() {
    var p int = -187
    var s uint = uint(p)
    fmt.Println(s)
}

We can run this code using the window below. Click “run” to see what happens.

You might imagine that running this code results in a compiler error, or that Go will print 187, the absolute value of -187. But what Go actually prints is 18446744073709551429!

STOP: Consider an update to func main() to have the two lines of code shown below. Again, what do you think will be printed?
func main() {
    m := 9223372036854775807
    fmt.Println(m+1)
}

If you think the answer is 9223372036854775808, then you would be wrong; it is -9223372036854775808! Where has the negative sign come from?

Both of these examples exhibit the same phenomenon, which is that any programming language allocates a finite amount of storage for variable, which means that there is a finite number of values that the variable can have. In Go (and many modern languages), variables with int type have the range of values between -263 and 263 – 1. This latter maximum value is what we declare m to be in the above code.

When we increase the value of m by 1, the variable overflows the space allocated for it, and the value of m wraps around and becomes the smallest allowable integer value, which is -263 = -9223372036854775808.

As for the other example in the code above, unsigned integers of type uint have values ranging between 0 and 264 – 1. When we try to convert -187 to an unsigned integer, the result is the same as if we started from 0 and subtracted 1 repeatedly 187 times. The first such subtraction wraps around to the maximum unsigned integer value (264 – 1), and so the result of the computation is 186 less than 264 – 1, or 264 – 186 = 18446744073709551429.

We should be wary of integer overflow if we are working with very large or very negative numbers, but for our purposes, we will see that the range of integers we have is usually sufficient. In a future chapter, we will explain more about integer overflow and how the maximum and minimum integer values are selected. For now, however, we have plenty more to say about programming fundamentals.

close

Love P4❤️? Join us and help share our journey!

Page Index