Packages in Go
Something you import inside your project and expect it to work, without asking too many questions on how it works under the hood. Right? 😸
Hello and welcome everyone, my name is Steve and I’m a YouTuber @ SteveHook. Consider checking it out before reading this article.
There is also a video tutorial version of this article, so be sure to check Packages in Go tutorial out.
So before we begin any coding or diagrams, let me show you where exactly you can find all the resources used inside this article and the video tutorial.
As you may expect everything is hosted on GitHub so make sure to check out Go Basics repository for more information. Also make sure to check out the presentations used inside the video tutorial and here in this article inside Packages directory.
Alright, so talk is cheap, that’s why without further ado, let’s go ahead and jump into the meat.
As soon as you create a file with the extension .go, the only required thing inside that file in order for it to compile successfully is the package declaration
And that’s it, the job is done. Now you have a package called
net and the compiler will not complain about it when building or running your program using
go run or
go build 😺
Packages in Go are organized across files. So in other words a package can consists of one or more files. So basically it can look like this:
It’s as simple as that, nothing fancy. As opposed to other languages where a file is considered a package.
In Go a file is considered a single piece from one package. If you’re familiar to terms like namespace, that’s pretty much what Go calls a package.
Generally speaking in Go there are 2 types of packages
So in case of the main package, you have to keep in mind that you need to have declared the
main function, which is the function which starts your program when ran. That’s why it’s called executable in the first place, there’s something which needs to be executed 😋
Also keep in mind, only executable packages generate binaries when compiled or built. Trying to run or build a non-main package will result in a compile error
So check out the following code. We have the main package, which imports
NewS1 function from
In this specific case, the services package is a non-main package, which provides a certain functionality only to be imported by other packages, including the package main.
On the other hand we have the main package, whose only job is to run the
main func which fires your program.
You can compile and run this program by simply typing:
# compiles your main program
# and all the packages which main imports
# and creates a binary in a temp location
# then runs that binarygo run main.go
# compiles your main program
# and all the packages which main imports
# and generates the executable binarygo build -o executable
Package naming & structure
There are only two hard things in Computer Science: cache invalidation and naming things.
— Phil Karlton
So as soon as you start creating files, packages and whole applications in pretty much any programming language, the 2 problems you will encounter when your applications start to grow are:
These 2 are always reasons for holy wars and endless debates across any team of developers, and this is no exception to Go
So why is this so important, and why we still don’t have a standard way of naming things or separating things? And how to do it right? 🤷♂️
Really a tricky question, and I still learn day by day how to do it better with experience.🤔
So enough wi the talk, let’s dive into some naming and separation best and bad practices (which are recommendations on how to write clean Go code)
Name your package the same way you name your directory
There are unfortunately still good libraries out there which do quite the opposite, they name the packages one way and the directories those packages live in another way.
This brings a lot of confusion and it is especially true when dealing with import paths, then you must know the name of that package or you have to alias the import path.
So this becomes weird quickly. Have a look at this import path
How do we know which is which? Well, apparently the package was named
newrelic but the directory it lives in was names
go-agent when the import could have looked like this
Which clearly tells the consumer, we have a package named
So no need for aliasing the import or guessing the package name.
Avoid naming your package using underscores or camelCase (multi words) in general
So the Go team in general says, naming your package that way, it’s just an example of poor naming.
A package name should be short, concise and very specific to what it does.
If a package has
under_score_here_and_there it only means that package is not clear and does more than it needs to do.
It also looks weird in the import path and inside the code
If the package is only tied to one single responsibility and good naming comes just as a bonus on top of that.
So, name your packages short, concise, one word (preferably) and to the point (exactly what the package )
Avoid naming like utils or misc
Again, just like in case of camelCase or under_score naming,
utils are considered bad practices, avoid them as much as possible.
It again proves that packages named this way do more than one specific thing and are likely to always change. This is a tradition which comes from other languages, and it’s also considered a bad practice in other languages too.
Avoid nesting directories and packages too much
So I’m not saying nesting is bad or something. Just keep in mind that it’s recommended to have your directory and package structure as flat as possible.
This way it makes your code more readable and understandable, it avoids friction and unnecessary engineering.
As a rule of thumb, try to keep it 1,2 or 3 levels nested maximum. In general the more flat the better, so think twice before nesting. Is that necessary? If not then just keep things simple.
Have a file named as your package name when it makes sense
So, usually and in most of the cases having a file for example named
network.go inside the network directory, where the network package is located is a good practice mainly for placing common functionality for the entire package in that file.
I usually create one, and common helpers and thing used across the entire package show up pretty quickly.
The last thing you want is an error which says:
'someHelper' is redeclared in this package
Because you forgot you declared inside another file, but in fact it’s actually you intend to use inside multiple files of the same package.
It’s a good practice, so make sure to have one file like this where makes sense, it keeps it more organized and more readable and understandable.
You cannot have multiple package declaration inside the same directory
I know this is an obvious one, but if you try this it’s just going to result in a compile error. Make sure all the packages inside a directory only belong to one and only one package.
You cannot do it otherwise so keep that in mind.
When importing a package in Go, the import path looks very specific. In most of the projects you will see something like this:
So as you can see nothing fancy here. In Go we are not tied to a specific central package registry. We can fetch packages from anywhere simply by importing them like this and using
go get to download them
# downloads httprouter package from github
# and stores it inside $GOPATHgo get github.com/julienschmidt/httprouter
or use the following command inside your project
# downloads every package which is referenced
# inside import paths from CWD (working dir)go get ./...
It’s as simple as that, for more info make sure to check out
go help packages
Now the previous syntax for import path very strongly related to something which in Go is called
This is a very short explanation on what
$GOPATH is but if you want to know more about it make sure to check out Demystifying $GOPATH tutorial on Youtube, where I explain more about it.
So what is
$GOPATH in general?
$GOPATH in simple words is the root directory for pretty much any Go project. It holds source code which you develop (not necessarily if you use Go modules), it also contains binaries and third party packages.
So here is a small diagram of how
$GOPATH really looks, it’s a simple as this:
So as you can see
$GOPATH is nothing fancy, just 3 directories
So in the end you’re gonna pay most of the attention to the
src which in most of the cases will be the place where your source code will live.
So when importing packages in Go, you can very easily import one package into the other and vice versa, causing an import loop. However Go got you covered, basically when you’ll compile your code it’s simply gonna fail.
So to give you a more concrete example of import loop aka
take a look at the following diagram:
So basically from the
main package we import package
p1 and it imports
p2 , but also notice
p1 which will make the program fail at compile time.
As I said, as opposed to other languages, import loops or cross reference is not allowed in Go, so keep that in mind. 😉
File globals & locals
So when you create any file in Go, you have to declare certain things in absolutely every file, others you only have to declare only once in at least one file of the same package and it will be visible inside the entire package (every file of the package)
So the first category I call them locals. Things you have to declare in every file of the same package. Basically in this category fall
The second category I call globals, things you have to declare only once to use everywhere inside the package, and cannot be redeclared in another file and they are:
So basically declaring any of these in at least one file once, will make them available inside the entire package
So just like in any other language, we have to be able to have private symbols (which are only visible for the internals) and public symbols or public API, code which can safely be consumed by other packages.
So Go handles this too, but a little bit taking a different approach. So in Go we do not have reserved keywords like
In fact we do not have classes either.
So in order to make something public or visible outside of the package we simply must Uppercase the symbol, whether it’s a constant, variable, function, type or field of a struct. That in Go is named exported
Anything else that does not start with an uppercase tells Go it’s private or un-exported
Here’s a small example to make things a little more clear:
The same rules apply when Marshaling or Unmarshaling JSON. And sometimes it’s tricky to catch the mistake, so it may just be an Uppercase issue. But also be mindful of what needs and does not need to be exported
So keep in mind, exported (public) requires Uppercase, anything else is un-exported. Especially pay attention at struct fields.
So similar to regular packages, in Go there are also test packages, or packages that are inside files which end up with
So any file whose name ends in
_test.go represents a test file in Go
A very important thing to keep in mind is even if you have exported symbols and they are inside a test package, you CANNOT IMPORT them inside another package, they are only available across the same package
So in case there is a need to create a common package for tests, which shares useful functionality across all tests, it’s advised to create a non-test package and store those helpers there.
We barely covered
go get and how it really works behind the scenes. Before I show you a small diagram I wanted to say that
go get is as well very related to
$GOPATH So here’s a small diagram explaining how it works:
So if we take the same example with the import paths and run
go get ./... inside the project. The steps are simple:
- Go ahead and fetch the package from
- Save the package inside
$GOPATH/pkgif you’re using a package manager or
It’s as simple as that, here’s a small HTTP server example using a third party router, simply to illustrate how
go get works.
When developing Go projects, there are some directories in Go, which are interpreted by the
go tool a little different. Some of these have a special meaning to the language others are more conventional and used widely by the community.
So the first 2, which I said have a special meaning to the language are:
vendor directory is an experiment introduced into the language since Go
1.5 version, and basically it serves the purpose of vendoring a project’s dependencies.
So when I talked about
go get I said that if you don’t use any sort of package manager it downloads the source code of third party packages and places it under
In the case when you use a package manager, you would typically use the
vendor directory. So the
vendor directory, looks exactly like
When you import a certain package, Go will pull it from the
vendor directory as opposed to
GOPATH/src if you have one inside your project.
When it comes to
internal directory, it is more suitable when developing libraries. Because of how it works.
So speaking of libraries, when you develop something thus provide a public API to the consumer of the package, because of the way
Exported/un-exported works in Go you may accidentally expose something which is exported to your library packages but only meant to be used internally.
To avoid these kind of incidents, when you place your code which have exported symbols inside
internal directory, those symbols will only be visible inside the library packages, and will not get exposed outside of the library, or to the consumer of the library.
So before making anything public think about it twice, maybe it’s better to just place it inside
When it comes to
pkg it is more of a conventional directory, which is there to emphasize that these are the packages which are publicly and safely available for consumption. So we could also say that this is as well more suitable for libraries.
Again this directory is not interpreted anyhow by the Go tool, so it’s more of a convention.
cmd directory is the same more of a conventional directory and you only need if it makes sense for your application. So you would want to place different commands there.
So if your application has multiple use cases, you could have multiple main packages and generate a binary for each specific case.
And in the end you would place those main commands inside
.dir, _dir and testdata
Also directories that start with
_ are ignored, such as
testdata directories. And this is what the official help tool says if you run
go help packages
However I’m not sure yet, in what ways these directories are ignored, other than the fact that dot directories and files in UNIX are invisible.
I tried running
go install and
go build from within these directories, and it definitely generated the binaries and the archives, so not sure how will they benefit my workflow. But I just wanted you to know they also exist
If you found this article useful, or the video tutorial useful, make sure to like, share, subscribe to know more about future upcoming content. Stay tuned. Peace! 🚀🚀🚀