Go is a language that encourages good software engineering practices. An important part of high-quality software is code reuse, applying the DRY principle – Don't Repeat Yourself. Enter Go packages, the next step into code reusability.
What is a package?
In the most basic terms, a Go package is a directory in your project containing one or more Go source files, or even other nested Go packages. The main purpose of a package is to help you organize related source files together into a single unit, making them reusable and maintainable.
Every Go source file belongs to a package; that's why every Go file starts with a package declaration:
package packagename
Any variable, type, or function within the source file belongs to its declared package, and other source files within the same package can access it.
Go source files that exist in the same directory must belong to the same package. Even though it is not required for a package to be named after its containing directory, it is a recommended convention to follow.
Importing a package
One of the most used built-in Go packages is fmt that provides I/O functionalities. To use this package in your code, you can import it below the package declaration as follows:
package packagename
import "fmt"
Upon importing the fmt package, you have access to the functions exported by the package. You can access these functions using the dot . operator, for example, fmt.Println() or fmt.Scanf().
Often, you will need to use more than one package in your program. You can import more than one package by using the following syntax:
import (
"fmt"
"math"
"strings"
)
In rare cases, such as when debugging unfinished code, you might have some unused packages in your code. To avoid a compiler error, you can use a blank _ identifier before the name of the package. For example:
import (
"fmt"
"math"
_ "os"
"strings"
)
Another use of the special blank identifier is to trigger the initialization init() function in some packages. For example, when working with database drivers, you can use a blank import to trigger the init() function and give the package the necessary data required to work with the database. Look at the example below when using Go to connect to a PostgreSQL database:
import (
"database/sql" // imports the Go database/sql package
_ "github.com/lib/pq" // imports the PostgreSQL sql drivers that the database/sql package requires
)
Inside the github.com/lib/pq package, the conn.go file contains the init() function. In simple terms, this will pass the necessary data to the database/sql package in your main package so that it can work with a PostgreSQL database:
func init() {
// when initialized with the blank "_" import, init() passes the necessary data
// to the database/sql package in your main package so that it can work with PostgreDB
sql.Register("postgres", &Driver{})
}
Another type of special import is the dot . import. It imports the package into the same namespace as the current package, so there is no need to use the imported package's name to access its functions. For example:
import (
. "fmt"
. "math"
)
func main() {
// Outputs 5 to the console, you can use Println without fmt and Abs without math prefix
Println(Abs(-5))
}
Finally, you can also import nested packages, for example:
import "math/rand"
Here math is the main package, and rand is the nested package. In this case, you would only be able to use the functions within the rand package, none of the math package functionalities would be available unless you imported both math and math/rand packages.
imported and not used compilation error.Package types
There are two types of packages in Go: executable and utility. An executable package holds an executable program for Go to compile and run. A utility package, in turn, is not self-executable; it just contains utility functions that can be used in an executable package. Examples of utility packages are fmt, os, math, etc.
A special executable package is the main package. To create an executable package in Go, there are two requirements:
-
The name of the package must be
main. -
It should contain a function called
main(), which will be the starting point of your program.
Below you can see the contents of a source file main.go that exists within the main package:
package main
import "fmt"
func main() {
fmt.Println("Learning about packages!")
}External packages
You can also install external packages to your project using the go get command. For example, let's try installing the fatih/color package hosted on GitHub:
$ go get github.com/fatih/color
Starting from Go version 1.16, the Module-aware mode is enabled by default. So if you're using Go version 1.16+ and want to install external packages, you'll need to perform the following steps:
-
Initialize a Go Module in your project by executing the
go mod init module-namecommand in your terminal, e.g.,go mod init example -
Then execute the
go get package-namecommand to install the external package, e.g.,go get github.com/fatih/color -
Finally, execute the
go mod tidycommand to add missing and remove unused modules in your project.
Now you can import the fatih/color package in your main.go file:
package main
import "github.com/fatih/color"
func main() {
// prints Hello Hyperskill in red font color
color.Red("Hello Hyperskill!")
}
After executing go run main.go you will see the following red-colored output in your terminal:
Hello Hyperskill!Internal packages
Last but not least, you can also create an internal package. Internal packages are special because they have to reside within the directory named internal in your project.
The go tool recognizes this internal directory and prevents the package it contains from being imported by any package that doesn't share the same parent directory or is not a subdirectory of this parent directory.
For example, let's look at the structure of the example directory that contains a nested internal package:
.
├───calculator
│ │ calculator.go
│ │
│ └───internal
│ modulo.go
│
└───main
main.go
The internal package example/calculator/internal can be imported by the code within calculator.go or any other Go source files within the example/calculator directory and its subdirectories because they share the common parent directory, example/calculator.
However, if you tried to import the example/calculator/internal package from the main package that resides in the example/main directory, you would get the following compilation error:
use of internal package example/calculator/internal not allowed
This happens because the example/main directory and the example/calculator/internal directory do NOT share the common parent directory, example/calculator.
Conclusion
Let's briefly remember what you've learned on this topic:
-
A Go package is just a directory that contains related Go source files;
-
There are two types of packages in Go: utility and executable packages. Utility packages are not self-executable; they just contain functions that can be used by the executable
mainpackage, which is the entry point of your program; -
You can install external packages to your project via the
go getcommand; -
Internal packages are a special type of package that should always reside within a directory named internal. Remember that internal packages can be imported only by packages that reside within the same parent directory as the internal package or its subdirectories.
Wow! This was a long topic, but there is more work to do... let's do some exercises now to test your knowledge about packages!