Your First Go Program
Packages, Files, and Go Tools
Goal
By the end of this lesson, your program is split across several files and two packages. You use the Go tools that format your code and find mistakes in it.
What you will learn
- How to split one package into several files
- How to create your own package and import it
- Exported and unexported names, which Go controls with capital letters
- How to use
go fmtandgo vet
Why this matters
Tasker grows to dozens of files. Real Go projects keep code organized in packages, so each package has one clear job. You learn the rules now, while the program is small.
Step 1: Split main into two files
Open the hello folder from Lesson 1.1. Create a new file named footer.go:
package main
import "fmt"
func printFooter() {
fmt.Println("Built with Go.")
}
Now change main.go so it calls this new function:
package main
import "fmt"
func main() {
fmt.Println("Hello from Tasker!")
printFooter()
}
Run the program:
go run .
Output:
Hello from Tasker!
Built with Go.
main.go uses printFooter, but it does not import footer.go. Both files start with package main and sit in the same folder, so they are one package. All files in a package can see each other's names.
Rule: one folder holds one package. Every file in the folder must use the same package name.
Step 2: Create your own package
Now you move the greeting into its own package. Create a folder named greeting inside hello. Inside it, create greeting.go:
package greeting
// Message returns a greeting for the given name.
func Message(name string) string {
return "Hello, " + name + "!"
}
Message takes a name and returns a new text value. You learn the details of functions in Lesson 1.3. For now, read it as: "give me a name, and I give you a greeting."
Your folder now looks like this:
hello/
├── go.mod
├── main.go
├── footer.go
└── greeting/
└── greeting.go
Step 3: Import your package
Change main.go:
package main
import (
"fmt"
"hello/greeting"
)
func main() {
fmt.Println(greeting.Message("Tasker"))
printFooter()
}
Run it:
go run .
Output:
Hello, Tasker!
Built with Go.
Look at the import path hello/greeting. It has two parts:
hellois the module name fromgo.mod.greetingis the folder inside the module.
Go finds every package in your project this way: the module name, then the folder path.
When you import more than one package, put them in one import ( ... ) block. By convention, standard library packages come first. A blank line separates them from your own packages.
Step 4: Exported and unexported names
In main.go, change greeting.Message to greeting.message (with a small m). Run the program. Go stops with an error like this:
./main.go:10:23: undefined: greeting.message
Now change it back.
Go has a simple rule:
- A name that starts with a capital letter is exported. Other packages can use it. Example:
Message,Println. - A name that starts with a small letter is unexported. Only code inside the same package can use it. Example:
printFooter.
Go has no public or private keywords. The first letter decides. This is why every function you call from fmt starts with a capital letter.
Notice the comment above Message. Go convention says each exported name has a comment that starts with the name itself. Go's documentation tools read these comments.
Step 5: Format your code with go fmt
Open greeting.go and make the formatting messy on purpose:
package greeting
// Message returns a greeting for the given name.
func Message( name string ) string {
return "Hello, "+name+"!"
}
Now run:
go fmt ./...
Open the file again. Go fixed the spacing and indentation.
The pattern ./... means "this folder and every folder inside it." Many Go commands accept it.
Go has one official code style, and go fmt applies it. Every Go project looks the same, so you can read any Go code easily. Most code editors run go fmt each time you save a file.
Step 6: Find mistakes with go vet
Create a file named bad.go in the hello folder:
package main
import "fmt"
func showCount() {
fmt.Printf("Count: %d\n", "ten")
}
%d asks Printf for a whole number, but the code gives it text. This code compiles, but the output would be wrong. Run:
go vet ./...
Output:
./bad.go:6:2: fmt.Printf format %d has arg "ten" of wrong type string
go vet finds code that compiles but is probably wrong. Delete bad.go when you are done.
Why it works: packages are Go's unit of code
- A module is the whole project. It has one
go.modfile. - A package is one folder of
.gofiles with the same package name. - An import path is the module name plus the folder path.
- The first letter of a name decides whether other packages can use it.
The compiler checks all of this. If you import a package and do not use it, or use a name that is not exported, the program does not compile.
Exercises
- Add a function. Add an exported function
Farewellto thegreetingpackage that returns"Goodbye, " + name + "!". Call it frommain. - Hide a helper. Add an unexported function
exclaimtogreetingthat adds"!"to the end of a text value. Use it insideMessageandFarewell. Then try to callgreeting.exclaimfrommain. What happens? - A second package. Create a package named
bannerwith an exported functionTitlethat returns"=== Tasker ===". Print it at the start ofmain. - Break the folder rule. In
greeting/greeting.go, changepackage greetingtopackage hello. Run the program and read the error. Change it back. - Vet it. Use
fmt.Printf("Name: %s\n", 42)somewhere in your code. Rungo vet ./.... Then fix the line.
Checkpoint
You are ready for the next lesson if you can:
- Explain the difference between a module and a package
- Create a package in a new folder and import it with the correct path
- Explain how Go decides if a name is exported
- Use
go fmt ./...andgo vet ./...and explain what each one does
