estructura-programa-go-paquetes-main

Structure of a Go Program: Packages and Imports

  • 5 min

A Go program is a set of packages that organize code and its dependencies, usually starting with a main package when we want to generate an executable.

In the previous article, we wrote our “Hello World.” It worked, we saw the text in the console, and we felt great. But, to be honest, we copied and pasted without fully understanding the structure we were using.

If you come from C#, Java, or C++, you’re used to seeing namespaces, classes, static methods, or different variants of main. In Go, things are different: organization revolves around packages.

Let’s dissect the anatomy of a Go program to understand packages, imports, and the main function.

Understanding this now will save us many headaches later. Go is very opinionated about how we should organize code.

Packages

The first statement in a .go file indicates which package it belongs to (comments or compiler directives may precede it).

package packagename
Copied!

In Go, the package is the basic unit of code organization. There are no classes as containers for static code. Every declaration belongs to a package.

main Package vs. Reusable Packages

Here is a critical distinction you must memorize:

  1. package main: It is special. It tells the compiler that this package can generate an executable program. It must contain a main() function that will be the entry point.
  2. Any other name: If the package is named calculator, utils, or models, Go treats it as a reusable package and will not generate an executable on its own.

The Directory Rule

Go has a very simple rule for organizing code:

All .go files within the same folder must belong to the same package.

You cannot have in the /utils folder one file saying package utils and another saying package tools. The compiler will throw an error. This forces your project’s folder structure to reflect the logical structure of your code.

Imports

Right below the package declaration, we indicate which other libraries we need to use.

In “Hello World,” we used import "fmt". In a real program, we will need more imports. Go allows us to group them so the code stays cleaner using a block in parentheses:

import (
    "fmt"
    "math"
    "time"
)
Copied!

Go’s “Obsession” with Cleanliness

If you come from other languages, you might be used to leaving imports “just in case” or having your IDE add them and forgetting to delete them.

In Go, that is a compilation error.

If you import "math" but don’t use any math function in your code, the program will not compile.

package main

import "fmt"
import "math" // ERROR: imported and not used: "math"

func main() {
    fmt.Println("Hello")
}
Copied!

Why are they so strict? Because unused imports slow down compilation and clutter the code. Go wants your code to be fast to compile and easy to read. What isn’t there, isn’t used.

Visibility: Exported vs. Unexported

In C# or Java, we use keywords like public, private, or protected to control who can access a variable or function.

In Go, those keywords do not exist. Visibility outside the package is determined by the first letter of the name:

  • Uppercase: the identifier is exported and can be used from other packages.
  • Lowercase: the identifier is unexported and can only be used within the same package.

Example:

package myPackage

// PublicFunction can be called from another file or package
func PublicFunction() {
    // Public implementation
}

// internalFunction can only be called from within myPackage
func internalFunction() {
    // Internal implementation
}
Copied!

This applies to functions, variables, constants, types, and struct fields. An initial uppercase letter tells us at a glance that the identifier is exported.

That’s why we use fmt.Println and not fmt.println. The Println function is public within the fmt package.

The main Function

Finally, we have the entry point. The main function is where the execution of our executable program begins.

func main() {
    // Your code starts here
}
Copied!

It has a couple of characteristics that differentiate it from C or C++:

  1. It has no arguments: It does not receive argc or argv directly. (To read console arguments, the os package is used; we’ll see it later).
  2. It returns no value: It does not return an int or status code. If the program terminates normally, the system assumes success (code 0). If we want to exit with an error, we must explicitly call os.Exit(1).

The Lifecycle

When we run the program, Go first initializes the imported packages and then the main package. Within each package, its variables are prepared, and its init() functions are executed before continuing:

  1. Packages that main depends on are initialized.
  2. Variables in the main package are initialized.
  3. Its init() functions are executed (if they exist).
  4. main() is executed.
  5. When main() finishes, the program terminates even if other goroutines are still running.

Complete Example Integrating Concepts

Let’s look at a slightly more elaborate example than a “Hello World” to see all this in action.

Imagine this file:

package main // 1. Executable package

import ( // 2. Grouped imports
	"fmt"
	"math/rand/v2" // Pseudo-random number generation
)

// A package-level variable (accessible throughout the file)
var programName = "Random Generator"

func main() { // 3. Entry point
	fmt.Println("Starting:", programName)

	number := generateNumber()

	fmt.Println("The chosen number is:", number)
}

// This function starts with a lowercase letter.
// It is PRIVATE. It can only be used in this package.
func generateNumber() int {
	return rand.IntN(100) // IntN is exported by the rand package
}
Copied!

Now that we understand the skeleton, in the next article, we’ll flesh it out: we’ll look at Variables, Constants, and Basic Data Types in Go. Let’s continue!