To be human is to be an unwilling passenger in a winding, aimless journey we call life. Each of us has felt the eternal solidarity of time break apart as we are thrust into existence to navigate the tribulations of existing, left only to wonder what the point of it all is. Just as we become complacent in our respective existential struggles, an event of unspeakable force shakes the foundations of our reality: we fall in love.
Falling in love is as exhausting as it is enchanting. Our lifespans only have the willing capacity to fall in love a finite number of times, if at all. This realization is responsible for making us wary of falling in love in the first place, as well as zealously defending the love we've found against potential intruders. I'm one to advocate for an opposite conclusion. As difficult, scary, or time-consuming as love may be, I argue that a life that has discovered love on multiple occasions is a life well-lived. If that makes me a slut, so be it: I am a slut for programming languages.
I've proudly maintained a long-term loving commitment to Python, as well as a complicated affair with JavaScript. Still, even this promiscuous lifestyle can leave one yearning for a kind of love that only a statically-typed language can deliver. As our relationships transition from scripting honeymoons to mature enterprise-level endeavors, it's natural to second-guess our choices. Why endure the overhead of a dynamically typed language when we end up annotating types with MyPy? Could there be validity in arguments that claim our language of choice is "slow?" And do we really think we can put up with baggage known as the GIL till death do us part?
If this all seems like a roundabout way to announce that I've been fooling around with Golang, that's because it absolutely is. I don't suspect that many people are willing or capable of leaving their comfort zones to partake in a journey of this magnitude. For the rest of you, I'd like to welcome you by my side for a short moment in our lives to explore the unexplored. Who knows, perhaps you'll even find love along the way.
Installation and Setup
Installing on OSX is simple thanks to Homebrew:
GOPATH vs GOROOT
Installing Golang via Homebrew automatically generates two directories critical to running Go:
- GOROOT (
/usr/local/go
): The Go "root" directory contains Go's source code. Homebrew will automatically register this path for you; there's little reason to mess around in here unless you're a Go contributor or if you're attempting to run multiple versions of Go. - GOPATH (
/Users/toddbirchard/go
): Unlike most programming languages, Go takes an opinionated stance that all projects and dependencies of the language should exist in a single directory known as the GOPATH. Any time we develop a Go project or install a third-party module, the actions taken ultimately happen inside this directory.
To make sure OSX recognizes our GOPATH, we'll have to add it to our shell's startup script. Open your .bashrc, .zshrc, or whatever it is you use:
We're going to add the /go base directory, as well as the subdirectory /go/bin:
Save this and reload your shell script:
Let's make sure everything went as planned:
And as a last bit of due diligence, let's confirm that our GOPATH is being recognized correctly:
As an aside, go
is Golang's CLI which is essential for compiling, formatting, and running Go code, as well as installing modules (we'll get to those in a sec). Try running go help
to get acquainted.
Anatomy of GOPATH
Golang's GOPATH is a directory where all your Go code and project dependencies live. The go
CLI actually has a built-in command $ go help gopath
which explains this quite well:
The Go path is used to resolve import statements.
It is implemented by and documented in the go/build package.
The GOPATH environment variable lists places to look for Go code.
Similar to how Python looks for imported libraries in the Python path, Go searches the GOPATH for the same. A notable difference between Python and Go paths is that Go expects all your Go projects to live within the GOPATH, specifically /go/src. Contrast this with Python where projects can live anywhere.
The GOPATH directory is made up of 3 subdirectories:
$ go help gopath
explains the purpose of each of these directories.
src:
The src directory holds source code. The path below src determines the import path or executable name.
pkg:
The pkg directory holds installed package objects.
As in the Go tree, each target operating system and architecture pair has its own subdirectory of pkg (pkg/GOOS_GOARCH).
bin:
The bin directory holds compiled commands.
Each command is named for its source directory, but only the final element, not the entire path.
In short, your personal source code belongs in /src, installed third-party packages will live in /pkg, and third-party commands which extend the go
CLI will live in /bin. To give an example, here's what my path looks like:
We should be able to break this down quite easily.
- /bin contains two Go commands I installed previously. golint is a third-party linter for Go, and "tour" is a local version of the official Tour of Go walkthrough to help Go newcomers learn their way around the language (I highly recommend completing this, btw).
- /pkg contains the packages I've installed. Pay special attention to /pkg/mod, where you can see I've installed several packages from Github under the/github.com directory. These packages include dataframe-go, which is a Go implementation of Pandas-like DataFrames, as well as mux, which is an HTTP router that we're going to use in our first hello-world project.
- /src has three projects I've worked on already. golang-helloworld is the project we're about to create in this tutorial.
Golang Terminology
Before we get to coding, let's brush up on some basic Go vocabulary:
- Packages: Go programs are made up of "packages," which mirror packaging concepts in other programming languages (think modules in Python or packages in Java). Every Golang program contains a package called main, which serves as the project's entry point.
- Modules: Go modules are third-party libraries installed by Go. Modules are essentially projects which have been published for general use as dependencies in your projects.
- Vendors: This is where things get interesting. While modules can be installed to the /pkg/mod directory for global use, source projects can contain their own versions of these modules to avoid clashing dependency versions between projects (this is not dissimilar to Python virtual environments). While not required, you can choose to keep module versions project-specific (we will do this in our example).
Creating a Hello World App
Enough chit-chat, let's make our first Go project. We start with creating our project's directory in the /go/src directory:
$ cd $GOPATH/src
$ mkdir golang-helloworld
$ cd golang-helloworld
While inside our new project directory, we're now going to initialize our project as a Go module. This means anybody will be able to install our Go code off Github if they so choose. I know I'm going to save my repo to github.com/hackersandslackers/golang-helloworld, so we run the following:
The moment this is done, a new file will appear in your directory called go.mod. Check out the contents using $ cat go.mod
to see what this initializes with:
Pretty simple stuff so far! go.mod contains information about our module for others, such as the module name and Go version it is intended for. As we install dependencies for our project, these dependencies and their respective versions will be stored here.
main.go
As mentioned, every Go project's entry point is a file called main.go. We're going to create the most simple main.go file imaginable: a script which outputs "Hello, world."
:
Now remember: since Go is a compiled language, we need to build our project before we can run it 😮. I know the extra effort is nearly unbearable, but things are about to pay off as you witness the fruits of your labor:
WE DID IT! We've just created our first "hello world" app in Go. Your project structure should now look something like this:
The newly created golang-helloworld file is the compiled executable which is created each time we run $ go build
. Each time we make changes to our source code, we should run $ go build
again to rebuild this executable with our changes.
Bonus: Code Formatting
A nifty tool that comes out-of-the-box in Go is a code formatter to clean up any ugly indents and such in your source. Try messing up the indents in main.go and run the following:
This should fix all the ugly formatting in the file names in outputs, in our case main.go.
Create a Web App
If we were to leave off with a stupid program that prints "Hello, world!"
, I'd be doing you a disservice. While we've set up Golang successfully, we haven't learned much about creating anything useful yet. It's time for us to kick things up a notch by making our app a web app which can be served from a browser.
Installing our First Dependency
To serve Go code via a web server, we're going to leverage the highly popular gorilla/mux module: a lightweight request router and dispatcher for matching incoming requests to their respective handler:
We're going to install this by running $ go get
followed by $ go install
:
go get
installs the source for gorilla/mux to our /go/bin directory. The -u
flag we pass is an "update" flag, which we use to grab the latest version just in case.
Let's see how go.mod was affected by running $ cat go.mod
:
As promised, our module dependency has now been added to go.mod along with the proper version number. Now we can import and use "github.com/gorilla/mux"
to help us build a project!
We can also use $ go mod vendor
to build this dependency in our /vendors folder to keep it local to our project.
Here's a wall of code which turns our hello world app into a web app:
Our functions are main()
, router()
, and handler()
, which get executed in that order.
main()
main()
sets up an HTTP server to be served locally on port 9100, with a couple read & write timeouts set as a form of best practice. Our server doesn't do much on its own without any routes to resolve. That's where our router()
function comes in.
router()
We initialize a "router" by created variable r with r := mux.NewRouter()
. From there we can set as many routes as we'd like with the following syntax:
HandleFunc()
is a built-in method to resolve URL routes. The first parameter is the target URL, and the second is the name of a function to be executed when a user requests said route. We only specify a single route in our example, but we could theoretically set as many as we'd like, for example:
handler()
Mux handler functions always accept two parameters by default, which essentially resolve to output and input. w http.ResponseWriter
expects a parameter named w with the type http.ResponseWriter, which is what we return to render something for the end-user. r *http.Request
contains information about the user's request, saved to a parameter named r.
We're keeping things simple(ish) today, so we'll settle for our route to simple output a "hello world" message for our route:
Rebuild and run our project with $ go build
and $ go run main.go
. Now try visiting 127.0.0.1:9100 in your browser:
And there you have it, lovebirds.
Best Practices
Before I leave to give you and your new favorite Gopher some private time, there's some very low-hanging fruit worth picking in an intro tutorial. This won't last long.
"Exported" Functions (AKA: Public Versus Private)
Nearly every programming language has the concept of "private" versus "public" functions. Go has this concept as well regarding shared functions between packages. Functions that are "shared" are referred to as "exported functions" (sup JavaScript).
A name is exported if it begins with a capital letter. Our hello world example consisted solely of private functions (which makes sense, as we only had a single package). If we wanted to make our router()
function accessible by other packages, we'd simply need to rename this to Router()
.
Type Declaration
Go expects that variables, incoming function parameters, and function return values to have declared types. In the below example, the function add()
accepts two integers and adds them, which undoubtedly results in an integer:
Variables are set using the same syntax as function parameters, with the variable name coming first:
There's also a shorthand way of setting multiple variables of the same type by separating variable names by commas. In this case, variables x
, y
, and z
are set as integers with no assigned values:
Short Assignment Statements
A very cool feature of Go is the :=
operator. The "short assignment" operator can be used to set multiple variables at once with implicit types. That means Go will resolve the type of each variable on its own based on the value assigned without the need for explicit type declaration. The below example creates three variables, where x
and y
are resolved as booleans, and z
is resolved as a string:
Constants
The last noteworthy nugget is the presence of constants in Go. While there's nothing unique about Go supporting constants, it's a breath of fresh air for Pythonistas who may be nostalgic about having the ability to do the following:
const Website = "hackersandslackers.com"
Happily Ever After?
Whether or not you hop on the Go train is a question of where your heart lies. While I'll continue using Python for the majority of what I do, it's nice to leave the Mrs. at home once in a while (inb4 this misogynistic analogy ruins me) to fool around building quick endpoints in a statically-typed language which isn't Java. Have I ever mentioned how much I hate Oracle? Like, how much I really hate them? No? Perhaps another time.
Anyway, get on with it then. The repository for what we've created today is up on Github here: