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¶
Create a project directory and change into it:
mkdir heygo && cd heygo
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 ago.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 orgo mod tidy
to scan your code for references to external packages, thego.mod
file is updated automatically.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 thefmt
package is imported from the Go standard library. Amain()
function is defined and a method is called fromfmt
to print a line of text that is passed as an argument.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
¶
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.
Run
go vet
on the file:got vet heygoV2.go vet: ./heygoV2.go:6:14: undefined: greeting
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}
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 themain
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.
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}
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.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
Continue to the
for
loop:(dlv) continue
Delve shows visually where you are in the code with
=>
; in this case, at the start of thefor
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:
Check the value of
sum
with theprint
command:(dlv) print sum
As expected,
sum
has been initialized to0
.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.
Check the value of the index:
(dlv) print i
This outputs
0
.Step again to confirm that the
sum
value has been incremented with the first element in thenumbers
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.
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 at0
. This means the loop attempts to run four times on three values. There is an off-by-one error.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)) }