Go Packages

Published

In Go, packages provide a powerful way to organize and structure your code. A key feature is that files within the same package have direct access to each other’s variables, functions, and types. This promotes seamless collaboration between different parts of your codebase. Let’s explore how this works in practice.

If two files share the same package name within the same directory, they can access each other’s names, such as variables and functions. We can execute package using the following command:

  go run *.go

All files within a directory sharing the same package name effectively share a namespace.

  ├── calculate
  │   ├── solution_runner.go
  │   └── types.go

In this example, importing the calculate package allows us to use all the names defined in both solution_runner.go and types.go.

How to import a package

Let’s assume we have the following folders structure. Here calculate and eiler folders are go packages.

  ├── calculate
  │   ├── solution_runner.go
  │   └── types.go
  ├── eiler
  │   ├── eiler_10
  │   │   └── eiler_10.go
  │   ├── eiler_6
  │   │   └── eiler_6.go
  │   ├── eiler_7
  │   │   └── eiler_7.go
  │   ├── eiler_8
  │   │   └── eiler_8.go
  │   └── eiler_9
  │       └── eiler_9.go
  └── go.mod

To import names from the eiler package into solution_runner, we need to create a go.mod file for our project.

  go mod init my-package-name

After running this command inside of folder we can see go.mod file

1
2
3
module my-package-name

go 1.22.2

All import paths should be relative to this path specified in go.mod.

File: calculate/solution_runner.go

1
2
3
4
5
6
7
8
package main

import "fmt"
import so "my-package-name/eiler/eiler_6"

func main() {
  fmt.Println(so.Solution(2000_000))
}

The location of the main file importing these modules doesn’t matter; only the path to the imported directory is important. For instance, if we have a package named eiler_10, all files within that directory can share variables and object names.

File: eiler/eiler_6/eiler_6.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package eiler_6

import "math"

func Solution(n Element) int {
  sum_of_quad := float64(0)
  sum_of_numbers := Element(0)
  for i := Element(1); i <= n; i++ {
    sum_of_quad += math.Pow(float64(i), 2)
    sum_of_numbers += i
  }
  quad_of_sum := math.Pow(float64(sum_of_numbers), 2)
  return int(quad_of_sum - sum_of_quad)
}

File: eiler/eiler_6/types_eiler_6.go

1
2
3
package eiler_6

type Element int

The Element type, defined in types_eiler_6.go, is used within eiler_6.go. When we import the eiler_6 package into our main function, we don’t encounter an error about Element being undefined. This is because it’s imported as part of the package.

We can also run everything as a package. If our main package has types defined in another file within that package, we can execute it like so:

  go run .
  go run calculate // if inside of parent directory
  go run main.go

If we attempt to run only one file from the package, we’ll get an error indicating that an imported name is undefined.

How to Use Local Modules Inside of Other Modules

Let’s create a module:

  mkdir mymodule
  cd mymodule

Inside mymodule, let’s create a go.mod file to mark this directory as a module for Go.

  go mod init mymodule

Within go.mod:

1
2
3
module mymodule

go 1.22.2

mymodule is an alias we can use when importing this module.

Let’s create inside of mymodule a package named calc to define some functionality.

  mkdir calc
  cd calc
  touch math.go

Inside math.go:

1
2
3
4
5
package calc

func AddInt(a, b int) int {
  return a + b
}

To test this module, let’s create a main module alongside mymodule.

  cd ../..
  mkdir main
  cd main
  go mod init main
  touch main.go

After all we’ll have the following folders structure:

  ├── main
  │   ├── go.mod
  │   ├── main.go
  │   └── utils.go
  └── mymodule
      ├── calc
      │   └── math.go
      └── go.mod

Inside main.go:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
package main

import (
  "fmt"
  "mymodule/calc"
)

func main() {
  fmt.Println(calc.AddInt(1, 2))
}

Inside of main directory

  go run .

If we try to run this, we’ll get an error:

  package mymodule/calc is not in GOROOT (/usr/local/go/src/mymodule/calc)

This error occurs because we haven’t specified where Go should find the mymodule module within main/go.mod. Go attempted to search in GOROOT but couldn’t find it and, since the module name doesn’t resemble a URL, emitted this error.

We need to explicitly tell the Go compiler where mymodule is located. If the module is only on our local machine, we use the replace directive.

1
2
3
4
5
6
module main

go 1.22.2

// location of mymodule relative to the main module
replace mymodule => ../mymodule

Let’s test again:

Inside the main directory

  go run  .

We’ll get almost equivalent error, but now we just need to get the module:

  package mymodule/calc is not in GOROOT (/usr/local/go/src/mymodule/cal)
  module mymodule provides package mymodule/calc and is replaced but not required; to add it:
  go get mymodule

Let’s run the suggested command:

  go get mymodule

Inside main/go.mod, we now have a new line:

  require mymodule v0.0.0-00010101000000-000000000000 // indirect

This indicates the specific version of the mymodule module that the Go compiler will use for building.

Running main.go one more time should now work successfully!