Go was designed to encourage good software engineering practices. One of the guiding principles of high-quality software is the DRY principle - Don’t Repeat Yourself, which basically means that you should never write the same code twice. You should reuse and build upon existing code as much as possible.
Functions are the most basic building blocks that allow code reuse. Packages are the next step into code reusability. They help you organize related Go source files together into a single unit, making them modular, reusable, and maintainable.
In this article, you’ll learn how to organize Go code into reusable packages, how to import a package, how to export a function, type, or variable to outside packages, and how to install 3rd party packages.
Let’s get started!
Go Package
In the most basic terms, A package is nothing but a directory inside your Go workspace containing one or more Go source files, or other Go packages.
Every Go source file belongs to a package. To declare a source file to be part of a package, we use the following syntax -
package <packagename>
The above package declaration must be the first line of code in your Go source file. All the functions, types, and variables defined in your Go source file become part of the declared package.
You can choose to export a member defined in your package to outside packages, or keep them private to the same package. Other packages can import and reuse the functions or types that are exported from your package.
Let’s see an example
Almost all the code that we have seen so far in this tutorial series include the following line -
import "fmt"
fmt
is a core library package that contains functionalities related to formatting and printing output or reading input from various I/O sources. It exports functions like Println()
, Printf()
, Scanf()
etc, for other packages to reuse.
Packaging functionalities in this way has the following benefits -
It reduces naming conflicts. You can have the same function names in different packages. This keeps our function names short and concise.
It organizes related code together so that it is easier to find the code you want to reuse.
It speeds up the compilation process by only requiring recompilation of smaller parts of the program that has actually changed. Although we use the
fmt
package, we don’t need to recompile it every time we change our program.
main
package and main()
function
The Go programs start running in the main
package. It is a special package that is used with programs that are meant to be executable.
By convention, Executable programs (the ones with the main
package) are called Commands. Others are called simply Packages.
The main()
function is a special function that is the entry point of an executable program. Let’s see an example of an executable program in Go -
// Package declaration
package main
// Importing packages
import (
"fmt"
"time"
"math"
"math/rand"
)
func main() {
// Finding the Max of two numbers
fmt.Println(math.Max(73.15, 92.46))
// Calculate the square root of a number
fmt.Println(math.Sqrt(225))
// Printing the value of `𝜋`
fmt.Println(math.Pi)
// Epoch time in milliseconds
epoch := time.Now().Unix()
fmt.Println(epoch)
// Generating a random integer between 0 to 100
rand.Seed(epoch)
fmt.Println(rand.Intn(100))
}
$ go run main.go
# Output
92.46
15
3.141592653589793
1538045386
40
Importing Packages
There are two ways to import packages in Go -
// Multiple import statements
import "fmt"
import "time"
import "math"
import "math/rand"
// Factored import statements
import (
"fmt"
"time"
"math"
"math/rand"
)
Go’s convention is that - the package name is the same as the last element of the import path. For example, the name of the package imported as math/rand
is rand
. It is imported with path math/rand
because It is nested inside the math
package as a subdirectory.
Exported vs Unexported names
Anything (variable, type, or function) that starts with a capital letter is exported, and visible outside the package.
Anything that does not start with a capital letter is not exported, and is visible only inside the same package.
When you import a package, you can only access its exported names.
package main
import (
"fmt"
"math"
)
func main() {
// MaxInt64 is an exported name
fmt.Println("Max value of int64: ", int64(math.MaxInt64))
// Phi is an exported name
fmt.Println("Value of Phi (ϕ): ", math.Phi)
// pi starts with a small letter, so it is not exported
fmt.Println("Value of Pi (𝜋): ", math.pi)
}
# Output
./exported_names.go:16:38: cannot refer to unexported name math.pi
./exported_names.go:16:38: undefined: math.pi
To fix the above error, you need to change math.pi
to math.Pi
.
Creating and managing custom Packages
Until now, We have only written code in the main
package and used functionalities imported from Go’s core library packages.
Let’s create a sample Go project that has multiple custom packages with a bunch of source code files and see how the same concept of package declaration, imports, and exports apply to custom packages as well.
Fire up your terminal and create a directory for our Go project:
$ mkdir packer
Next, we’ll create a Go module and make the project directory the root of the module.
Note: Go module is Go’s new dependency management system. A module is a collection of Go packages stored in a directory with a go.mod file at its root. The go.mod file defines the module’s path, which is also the import path used while importing packages that are part of this module.
Before Go module got introduced in
Go 1.11
, every project needed to be created inside the so-calledGOPATH
. The path of the project insideGOPATH
was considered its import path.
We’ll learn more about Go modules in a separate article.
Let’s initialize a Go module by typing the following commands:
$ cd packer
$ go mod init github.com/callicoder/packer
Let’s now create some source files and place them in different packages inside our project. The following image displays all the packages and the source files:
Here is the code inside every source file of our project -
numbers/prime.go
package numbers
import "math"
// Checks if a number is prime or not
func IsPrime(num int) bool {
for i := 2; i <= int(math.Floor(math.Sqrt(float64(num)))); i++ {
if num%i == 0 {
return false
}
}
return num > 1
}
strings/reverse.go
package strings
// Reverses a string
/*
Since strings in Go are immutable, we first convert the string to a mutable array of runes ([]rune),
perform the reverse operation on that, and then re-cast to a string.
*/
func Reverse(s string) string {
runes := []rune(s)
reversedRunes := reverseRunes(runes)
return string(reversedRunes)
}
strings/reverse_runes.go
package strings
// Reverses an array of runes
// This function is not exported (It is only visible inside the `strings` package)
func reverseRunes(r []rune) []rune {
for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 {
r[i], r[j] = r[j], r[i]
}
return r
}
strings/greeting/texts.go (Nested package)
// Nested Package
package greeting
// Exported
const (
WelcomeText = "Hello, World to Golang"
MorningText = "Good Morning"
EveningText = "Good Evening"
)
// Not exported (only visible inside the `greeting` package)
var loremIpsumText = `Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
commodo consequat.`
main.go (The main package: entry point of our program)
package main
import (
"fmt"
str "strings" // Package Alias
"github.com/callicoder/packer/numbers"
"github.com/callicoder/packer/strings"
"github.com/callicoder/packer/strings/greeting" // Importing a nested package
)
func main() {
fmt.Println(numbers.IsPrime(19))
fmt.Println(greeting.WelcomeText)
fmt.Println(strings.Reverse("callicoder"))
fmt.Println(str.Count("Go is Awesome. I love Go", "Go"))
}
# Building the Go module
$ go build
The above command will produce an executable binary. Let’s execute the binary file to run the program:
# Running the executable binary
$ ./packer
true
Hello, World to Golang
redocillac
2
Things to note
Import Paths
All import paths are relative to the module’s path
github.com/callicoder/packer
.import ( "github.com/callicoder/packer/numbers" "github.com/callicoder/packer/strings" "github.com/callicoder/packer/strings/greeting" )
Package Alias
You can use package alias to resolve conflicts between different packages of the same name, or just to give a short name to the imported package
import ( str "strings" // Package Alias )
Nested Package
You can nest a package inside another. It’s as simple as creating a subdirectory -
packer strings # Package greeting # Nested Package texts.go
A nested package can be imported similar to a root package. Just provide its path relative to the module’s path
github.com/callicoder/packer
-import ( "github.com/callicoder/packer/strings/greeting" )
Adding 3rd party Packages
Adding 3rd party packages to your project is very easy with Go modules. You can just import the package to any of the source files in your project, and the next time you build/run the project, Go automatically downloads it for you -
package main
import (
"fmt"
"rsc.io/quote"
)
func main() {
fmt.Println(quote.Go())
}
$ go run main.go
go: finding rsc.io/quote v1.5.2
go: downloading rsc.io/quote v1.5.2
go: extracting rsc.io/quote v1.5.2
go: downloading rsc.io/sampler v1.3.0
go: extracting rsc.io/sampler v1.3.0
go: downloading golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
go: extracting golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
go: finding rsc.io/sampler v1.3.0
go: finding golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
Don't communicate by sharing memory, share memory by communicating.
Go will also add this new dependency to the go.mod
file.
Manually installing packages
You can use go get
command to download 3rd party packages from remote repositories.
$ go get -u github.com/jinzhu/gorm
The above command fetches the gorm
package from Github and adds it as a dependency to your go.mod
file.
That’s it. You can now import and use the above package in your program like this -
import "github.com/jinzhu/gorm"
Conclusion
That’s all folks! In this article, you learned how to organize Go code into reusable packages, how to import a package, how to export members of a package, how to create custom packages, and how to install third party packages.
You’ll learn about package initialization, blank identifier, and package documentation in a future article.
Next Article: Working with Arrays in Golang
Code Samples: github.com/callicoder/golang-tutorials