Develop with Go on Ubuntu

This tutorial shows how to build, run, and debug Go programs on Ubuntu. For instructions on how to install Go and related tooling, including the Delve debugger, see the dedicated guide on How to set up a development environment for Go on Ubuntu. This article assumes that tooling suggested in that article has been installed.

Creating a Go project

  1. Create a project directory and change into it:

    mkdir heygo && cd heygo
    
  2. Initialize the project as a Go module. When initializing a module, provide the URL of a repository for hosting the source. For testing purposes, use a generic URL:

    go mod init youruser.github.com/heygo
    

    The go mod init command creates a go.mod file in the project root that tracks the version of Go used for the program and any external dependencies.

    Note

    When you use go get <package> to fetch a specific package or go mod tidy to scan your code for references to external packages, the go.mod file is updated automatically.

  3. Create a heygo.go file with the following content:

    heygo.go
    package main
    
    import "fmt"
    
    func main() {
        fmt.Println("Hey Go!")
    }
    

    Here, the name of the package, main, is identified, and the fmt package is imported from the Go standard library. A main() function is defined and a method is called from fmt to print a line of text that is passed as an argument.

  4. To compile and run your program, use the go run command. If you pass the current directory as an argument (.), the Go compiler automatically finds the main package and main function during the build:

    go run .
    

    This outputs:

    dev@ubuntu:~/heygo$ go run .
    Hey Go!

Improving Go code with the help of tooling

Tooling built in Go, including go vet and gofmt, can be used to debug and format code. Delve is recommended for advanced debugging.

Go vet and gofmt

  1. In the same directory where you initialized the module, delete heygo.go and replace it with a new file, heygoV2.go:

    heygoV2.go
    1package main;
    2
    3import "fmt";
    4
    5func main() {
    6fmt.Println(greeting);
    7}
    

    This code contains a bug and is poorly formatted.

  2. Run go vet on the file:

    got vet heygoV2.go
    vet: ./heygoV2.go:6:14: undefined: greeting
    
  3. Fix the error by defining the greeting variable:

    heygoV2.go
    1package main
    2
    3import "fmt";
    4
    5+var greeting="Hey Go!"
    6
    7func main() {
    8fmt.Println(greeting)
    9}
    
  4. Running gofmt with the -w parameter on the file identifies formatting issues and writes necessary changes to the file:

    gofmt -w heygoV2.go
    

    In this case, unneeded semicolons are removed from the import line, and the call to the print method in the main function is indented correctly:

    heygoV2.go
     1package main
     2
     3- import "fmt";
     4+ import "fmt"
     5
     6var greeting="Hey Go!"
     7
     8func main() {
     9-fmt.Println(greeting)
    10+	fmt.Println(greeting)
    11}
    

Debugging with Delve

Delve is a popular debugger for Go code. Many editors, including VSCode and GoLand, support Delve. In this guide, Delve is used as a command-line debugging tool.

  1. Create a file to debug called main.go in a new folder where you have initialized a Go module.

    This program is intended to calculate the average value from an array of integers. However, there is a bug in the for loop that needs to be investigated:

    main.go
     1package main
     2
     3import "fmt"
     4
     5func calculateAverage(numbers []int) float64 {
     6  sum := 0
     7  for i := 0; i <= len(numbers); i++ {
     8    sum += numbers[i]
     9  }
    10  return float64(sum) / float64(len(numbers))
    11}
    12
    13func main() {
    14  numbers := []int{10, 45, 30}
    15  average := calculateAverage(numbers)
    16
    17  fmt.Printf("Average value of  is: %.2f\n", average)
    18}
    
  2. Initiate a debugging session with Delve by running dlv debug on the file:

    dlv debug main.go
    

    This puts you in an interactive debugging session. You can interact with the debugger by entering commands after the (dlv) prompt:

    Type 'help' for list of commands.
    (dlv)
    

    To exit at any time:

    (dlv) exit
    

    Delve is used in this example to debug the calculateAverage function. You need to be in a debugging session, indicated by the (dlv) prompt.

  3. Set a break point at line 6:

    (dlv) break main.go:6
    

    If the break point is set successfully, you get the following message:

    Breakpoint 1 set at 0x49cee9 for main.calculateAverage() ./main.go:6
    
  4. Continue to the for loop:

    (dlv) continue
    

    Delve shows visually where you are in the code with =>; in this case, at the start of the for loop:

    > [Breakpoint 1] main.calculateAverage() ./main.go:7 (hits goroutine(1):1 total:1) (PC: 0x49cee9)
        2:
        3:  import "fmt"
        4:
        5:  func calculateAverage(numbers []int) float64 {
        6:    sum := 0
    =>   7:   for i := 0; i <= len(numbers); i++ {
        8:      sum += numbers[i]
        9:    }
        10:   return float64(sum) / float64(len(numbers))
        11: }
        12:
    
  5. Check the value of sum with the print command:

    (dlv) print sum
    

    As expected, sum has been initialized to 0.

  6. Step through the for loop with:

    (dlv) step
    

    Again, your position in the code is shown:

    > main.calculateAverage() ./main.go:8 (PC: 0x49cf09)
        3:  import "fmt"
        4:
        5:  func calculateAverage(numbers []int) float64 {
        6:    sum := 0
        7:    for i := 0; i <= len(numbers); i++ {
    =>   8:     sum += numbers[i]
        9:    }
        10:   return float64(sum) / float64(len(numbers))
        11: }
        12:
        13: func main() {
    

    Note

    This output showing the code position is truncated for the remainder of this guide.

  7. Check the value of the index:

    (dlv) print i
    

    This outputs 0.

  8. Step again to confirm that the sum value has been incremented with the first element in the numbers array:

    (dlv) step
    ...
    ...
    (dlv) print sum
    10
    (dlv) print numbers
    []int len: 3, cap: 3, [10,45,30]
    

    So far so good; the sum is equal to the first element of the list.

  9. Keep stepping through the code until you find the bug:

    (dlv) step
    ...
    ...
    (dlv) print i
    1
    (dlv) step
    ...
    ...
    (dlv) print sum
    55
    (dlv) step
    ...
    ...
    (dlv) print i
    2
    (dlv) step
    ...
    ...
    (dlv) print sum
    85
    (dlv) step
    ...
    ...
    (dlv) print i
    3
    (dlv) step
    
    

    The last step causes a panic:

    > [unrecovered-panic] runtime.fatalpanic() /usr/local/go/src/runtime/panic.go:1217 (hits goroutine(1):1 total:1) (PC: 0x43a604)
    Warning: debugging optimized function
      runtime.curg._panic.arg: interface {}(string) "runtime error: index out of range [3] with length 3"
    

    The array has a length of 3, but the index is initialized at 0. This means the loop attempts to run four times on three values. There is an off-by-one error.

  10. Change the code as follows to fix the error:

    func calculateAverage(numbers []int) float64 {
      sum := 0
    -	for i := 0; i <= len(numbers); i++ {
    +	for i := 0; i < len(numbers); i++ {
        sum += numbers[i]
      }
      return float64(sum) / float64(len(numbers))
    }