Go Language Modularization
Internal
Overview
Go modularization builds upon the concepts of package and module. Packages provide a namespace for their members, and they are a way to encapsulate code, hide implementation details and only expose features, such as variables, functions or type definitions that are meant to be publicly consumed. Packages can be published as part of modules. Modules have been introduced in Go 1.11.
Packages
Standard Library
Go comes with a set of over 100 "built-in" packages, which are available as part of the locally installed Go development environment.
Standard library package documentation is available online here:
The standard library is a good source of code examples, comments and style guidance.
Standard library packages:
pkg.go.dev
The place to look for published third-party packages is
Modules
Module-Aware or GOPATH Mode
The compiler must locate packages on the local file system every time it handles an import
statement.
The go tool has two modes of resolving package dependencies: module-aware mode or GOPATH mode.
In module-aware mode, the go
commands use go.mod
files to find versioned dependencies and typically load packages out of the module cache, downloading modules if they are missing. As of Go 1.16, the module-aware mode is enabled by default, regardless of whether go.mod
is present or not. The behavior can be controlled with the GO111MODULE
environment variable.
In GOPATH mode, go
commands use the value of the GOPATH
environment variable and vendor directories to resolve packages.
Also see:
Workspace
The workspace is a concept introduced in Go 1.18. A workspace allows organizing the code for a project that has several modules which share a common list of dependencies. The workspace maintains metadata, especially dependency metadata, in a file called go.work
. The dependencies declared in this file can span modules and anything declared in go.work
will override dependencies in the modules's go.mod
. The packages and modules maintained in a workspace are managed with the go
tool.
A workspace may contain multiple projects.
The standard workspace layout is:
. ← GOPATH should point to this directory, it contains src, pkg and bin │ ├─ src │ ├─ a │ │ └─ b │ │ └─color # "color" package directory, with the "a/b/color" import path │ │ ├─ colors.go │ │ ├─ aux.go │ │ └─ ... │ │ │ ├─ weight # "weight" package directory, with the "weight" import path │ │ ├─ weights.go │ │ ├─ aux.go │ │ └─ ... │ │ │ ├─ novaordis.com │ │ └─ tools │ │ └─ hammer # "hammer" package directory, with the "novaordis.com/tools/hammer" import path │ │ └─ ... │ │ │ └─ github.com │ └─ blue-org │ └─ tools │ └─ wrench # "wrench" package directory, with the "github.com/blue-org/tools/wrench" import path │ ├─ .git │ └─ ... │ ├─ pkg │ └─ darwin_amd64 │ ├─ weights.go │ ├─ a/b/color.a │ ├─ novaordis.com/tools/hammer.a │ └─ github.com/bue-org/tools/wrench.a └─ bin
Content
src
The src
subdirectory holds source code. Each package resides in a directory whose name relative to ${GOPATH}/src
represents the package's import path.
The src
subdirectory may contain multiple version-control repository workareas.
pkg
The build tool stores compiled packages in the pkg
directory, under ${GOOS}_${GOARCH}
subdirectories.
bin
The bin
directory is where the executables are stored.
Relationship between Workspace and GOPATH
GOPATH
should point to the root of the workspace, the directory that contains src
, pkg
and bin
. Further research is required.
Program
Go programs are constructed by linking together packages. There must be a main
package, which contains the main()
, to trigger the linker.
Project
Repository
A Go repository typically contains only one module, located in the root of the repository.
TO DEPLETE
Writing a Package
Place the package files under a directory hierarchy that makes it obvious where the package comes from, if imported. For example, if planning to write a reusable "blue" package as part of the "airplane" project, place the package source under:
$LOCAL_PROJECT_HOME/src/io/novaordis/airplane/blue
Thus the package will be imported as:
import io/novaordis/airplane/blue
Exported Identifiers
In order to be visible outside the declaring package, an identifier must be exported. This is achieved by any of the following is true:
- The identifier's first character is an upper case letter.
- The identifier is declared in the package block or it is a field name or a method name of an exported type.
An identifier that is not explicitly exported is unexported.
Note that identifiers, and not values, can be exported or unexported.
It is a good practice to only expose the package elements that we explicitly want other package to use, and hide everything else. This allows to change the hidden parts later, without affecting the package's consumers.
Note that just because an identifier is unexported, it does not mean other packages can't indirectly access it: for example a function can return the value of an unexported type and this value is accessible by an calling function, even if the calling function has been declared in a different package. This can be coupled with the short declaration operator (:=), which infers the type of the variable. A variable of that type cannot be explicitly declared with var.
struct Fields Export
The fields of an exported struct type can be exported or unexported on a field basis, by naming them with an upper case and respectively lower case letter.
Embedded Type Export
If the name of an inner type starts with a lower case, it is unexported even if its outer type is exported. Even if the name of the inner type is not exported, its fields may be individually exported, and thus accessible from outside the package.
For more details on embedded types see:
The "main" Package
A package named "main" that exposes a main() function is used by the Go linker as a start point for walking the package dependency graph when building the binary executable. All of the executable programs must have a package called "main", and their "main" package must have a main() function, otherwise the executable won't be built. More about the main() function, the main package and Go executables can be found here: compiling an executable.
Package Block
Each package has a package block containing all source text for that package. More about blocks is available here:
Vendoring
"Vendoring" is the act of making a local copy of a third party package your project depends on. This copy is traditionally placed inside each project and then saved in the project repository.
Vendoring is using local copies of external dependencies to satisfy imports of those dependencies.
For a directory structure referred by GOPATH, code below a directory named "vendor" is importable only by code in the directory tree rooted at the parent of "vendor", and only using an import path that omits the prefix up to and including the vendor element.
Code in vendor directories deeper in the source tree shadows code in higher directories. Within the subtree rooted at foo, an import of "crash/bang" resolves to "foo/vendor/crash/bang", not the top-level "crash/bang". Code in vendor directories is not subject to import path checking.
When 'go get' checks out or updates a git repository, it now also updates submodules.
Vendor directories do not affect the placement of new repositories being checked out for the first time by 'go get': those are always placed in the main GOPATH, never in a vendor subtree.