Introduction
In the previous section, you completed a basic Go program, which included the following lines of code:
package main
import "fmt"
How do we understand these two lines of code? How do we use the package and import statements effectively?
In this lab, you will learn how to create and import packages in Go. This will enable you to organize your code into reusable modules, making your Go projects more maintainable and scalable.
Knowledge Points:
- Definition and declaration of a package
- Understanding exported (public) and unexported (private) identifiers
- Different forms of importing packages: single, grouped, dot, alias, and anonymous imports
Declaring and Defining Packages
A package in Go is similar to modules in Python or libraries in C. It is a collection of source code files used to organize and reuse code. Every Go file must declare a package at the beginning of the file.
Note: A Go program must have one and only one package named
main, which serves as the entry point for execution. Without it, the program cannot generate an executable file.
Key Points:
- Exported (Public) Identifiers: Identifiers (variables, functions, types, etc.) that begin with an uppercase letter are accessible from other packages. Think of these as the public interface of your package.
- Unexported (Private) Identifiers: Identifiers that begin with a lowercase letter are only accessible within the same package. These are considered internal implementation details of the package.
- Package Cohesion: All files in the same folder must belong to the same package. This ensures that related code stays together.
- Package Naming Conventions: Package names should be lowercase, short, and descriptive, avoiding underscores or capital letters.
Let's create our own custom package:
Create a folder named
propagandistand a filepropagandist.goinside it:mkdir ~/project/propagandist touch ~/project/propagandist/propagandist.goWrite the following code in
propagandist.go:package propagandist var Shout = "I Love LabEx" // Public variable var secret = "I love the dress" // Private variable func Hit() string { return "Don't hit me, please!" }Shoutis public because it starts with an uppercase letter. This means you can access it from other packages that importpropagandist.secretis private because it starts with a lowercase letter. It can only be used within thepropagandistpackage.Hitis a public function, accessible from other packages.
Initialize a Go module for the package:
cd ~/project/propagandist go mod init propagandistThis command initializes a new Go module in the
propagandistdirectory, which helps manage dependencies for the package.
Single-Item Import
To use the propagandist package, let's create a new Go program. This step will demonstrate how to import and use a single package in your Go code.
Create a new Go file named
pacExercise.goin the project folder:touch ~/project/pacExercise.goInitialize a Go module for the program:
cd ~/project go mod init pacExerciseUpdate the
go.modfile to include the local package dependency, run the following command in the terminal:echo "replace propagandist => ./propagandist" >> go.modImportant: This command adds a
replacedirective to yourgo.modfile. This is crucial because it tells Go that thepropagandistpackage should be sourced from the local directory./propagandistinstead of attempting to download it from a remote repository. You should execute this command in your terminal, which will append the linereplace propagandist => ./propagandistto yourgo.modfile. Don't directly write this line into the file manually.Write the following code in
pacExercise.goto import and use thepropagandistpackage:package main import ( "fmt" "propagandist" ) func main() { fmt.Println(propagandist.Shout) // Access the public variable }- This code imports the
fmtpackage for printing output and thepropagandistpackage. - It then accesses the public variable
Shoutfrom thepropagandistpackage usingpropagandist.Shout.
- This code imports the
Run the program:
go mod tidy go run pacExercise.goThe
go mod tidycommand ensures that yourgo.modfile is updated with any new dependencies. Thego run pacExercise.gocommand compiles and executes the program.Expected Output:
I Love LabEx
Grouped Imports
When importing multiple packages, you can use grouped imports for better readability and organization. This is a stylistic choice and doesn't change the functionality of your code.
Modify
pacExercise.goto use grouped imports:package main import ( "fmt" "propagandist" ) func main() { fmt.Println(propagandist.Shout) }In the code snippet above, the packages
fmtandpropagandistare imported inside a singleimportblock enclosed in parentheses. This makes it easier to read and manage multiple package imports. This is exactly the same as the previous example and shows how to use the grouped import syntax.Run the program to confirm it still works:
go run pacExercise.goThe program should execute without errors and output the same result as before.
Dot Import
Using a dot import, you can omit the package name prefix when calling its functions or variables. This is often discouraged in favor of explicit package names because it can lead to namespace conflicts and reduce readability. However, it's good to know what it is.
Modify
pacExercise.goto use a dot import forfmt:package main import . "fmt" import "propagandist" func main() { Println(propagandist.Shout) // No `fmt.` prefix needed }
- Here,
import . "fmt"means you can use functions and variables from thefmtpackage directly without thefmt.prefix. - For example, you use
Printlninstead offmt.Println.
Run the program:
go run pacExercise.goExpected Output:
I Love LabEx
Alias Import
You can alias an imported package for clarity or to avoid conflicts when two packages have similar names. This is helpful for making your code more readable and managing namespace collisions.
Modify
pacExercise.goto aliasfmtasio:package main import io "fmt" import "propagandist" func main() { io.Println(propagandist.Shout) // Use the alias `io` instead of `fmt` }import io "fmt"creates an aliasiofor thefmtpackage.- Now, you use
io.Printlninstead offmt.Println.
Run the program:
go run pacExercise.go
Anonymous Import
Anonymous imports are used to import a package for its side effects, such as running its init() function, without needing to directly reference any of its exported identifiers. This is useful for packages that register drivers or perform other initialization tasks.
Modify
pacExercise.goto include an anonymous import fortime:package main import ( "fmt" "propagandist" _ "time" // Anonymous import ) func main() { fmt.Println(propagandist.Shout) }import _ "time"is an anonymous import. The underscore_is used as a blank identifier, telling the compiler that you are importing the package for its side effects and won't be referencing anything from it directly in your code.- The
timepackage'sinit()function will execute when this program runs. Thetimepackage does not have any particular side effects visible here, however, many packages use this to register database drivers, or configuration settings.
Run the program:
go run pacExercise.goExpected Output:
I Love LabEx
Understanding the init() Function
The init() function is a special function in Go that plays a crucial role in package initialization. It's automatically executed by Go when a package is imported, before any other code in the main function runs. This section will explain the details of init() functions and how they work within Go's initialization process.
Key Points About init() Functions:
Definition and Purpose:
- An
init()function has no parameters and no return values:func init() {} - It's used for package initialization tasks such as setting up initial states, registering drivers, or validating prerequisites.
- An
Execution Order:
- Go guarantees that package initialization occurs only once, even if a package is imported multiple times.
- The initialization follows a well-defined order:
- Package level variables are initialized first
- Then
init()functions are executed - Finally, the
main()function runs (only in the main package)
Multiple
init()Functions:- A single Go file can contain multiple
init()functions - Multiple files in the same package can each have their own
init()functions - All these
init()functions will be executed, but the order within the same package is not guaranteed
- A single Go file can contain multiple
Dependency Chain:
- When packages import other packages, Go ensures that the
init()functions in dependencies are executed first - This creates a bottom-up initialization flow: deepest dependencies initialize first
- When packages import other packages, Go ensures that the
Let's create a practical example to demonstrate how init() functions work:
First, let's modify our
propagandistpackage to include aninit()function. Updatepropagandist.go:package propagandist import "fmt" var Shout = "I Love LabEx" // Public variable var secret = "I love the dress" // Private variable var initialized bool func init() { fmt.Println("Initializing propagandist package...") initialized = true } func Hit() string { return "Don't hit me, please!" } func IsInitialized() bool { return initialized }Now, let's create another file in the propagandist package to demonstrate multiple
init()functions:touch ~/project/propagandist/second.goAdd the following content to the file:
package propagandist import "fmt" func init() { fmt.Println("Second init function in propagandist package...") }Create a new helper package to demonstrate initialization order:
mkdir -p ~/project/helper touch ~/project/helper/helper.goAdd the following content to the file:
package helper import "fmt" var Message = "Helper package is ready" func init() { fmt.Println("Initializing helper package...") } func GetMessage() string { return Message }Add the module file for the helper package:
cd ~/project/helper go mod init helperUpdate your
pacExercise.goto use both packages and demonstrate the initialization order:package main import ( "fmt" "helper" "propagandist" ) func init() { fmt.Println("Initializing main package...") } func main() { fmt.Println("Main function is running") fmt.Println(propagandist.Shout) fmt.Println(helper.Message) fmt.Printf("Propagandist initialized: %v\n", propagandist.IsInitialized()) }Update the
go.modfile in the main project to include the local helper package:cd ~/project echo "replace helper => ./helper" >> go.mod go mod tidyRun the program and observe the initialization order:
go run pacExercise.goExpected Output (the exact order of the two propagandist init functions might vary):
Initializing helper package... Initializing propagandist package... Second init function in propagandist package... Initializing main package... Main function is running I Love LabEx Helper package is ready Propagandist initialized: true
This output demonstrates the key aspects of Go's initialization process:
- Dependent packages are initialized before the packages that import them
- Within a single package, all
init()functions will run (though their order is not guaranteed) - The
main()function runs only after all package initializations are complete - Package-level variables are initialized before any
init()functions run
This initialization sequence is an important consideration when designing Go packages, especially when managing dependencies or performing setup operations that must happen in a specific order.
Summary
In this lab, you learned:
- How to create and define custom packages in Go, encapsulating reusable code.
- The difference between public (exported) and private (unexported) identifiers and how they impact accessibility.
- Various ways to import packages, each with its use case:
- Single-item import: Importing one package at a time.
- Grouped import: Importing multiple packages in a single block for better organization.
- Dot import: Importing a package and using its identifiers directly without the package name prefix. (Use with caution)
- Alias import: Renaming imported packages for better readability or to avoid naming conflicts.
- Anonymous import: Importing a package solely for its side effects, such as initialization.
- The role of the
init()function in packages and how anonymous imports can trigger its execution. - The detailed workings of Go's initialization process, including:
- How package-level variables are initialized before
init()functions - The guaranteed execution order of
init()functions across dependent packages - How multiple
init()functions work within a package - The complete initialization flow from dependent packages to the main function
- How package-level variables are initialized before
By completing this lab, you are now equipped to structure and manage Go projects using packages effectively. You can create reusable modules, control access to identifiers, better organize your code, and understand the initialization process, leading to more maintainable and scalable Go applications.



