Tenetur itaque dolorum mollitia facere reprehenderit praesentium ullam. Accusantium id et illo ut eum. Dolore rerum qui sit eveniet ut nesciunt quam. Molestias fugiat omnis laborum quam.

Programmers that come to Go from Python often wonder "do I need something like
virtualenv here?"

The short answer is NO; this post will provide some additional details.

While virtualenv in Python is useful in many situations, I think it’d be
fair to divide them into two broad scenarios: for execution and for development.
Let’s see what Go offers for each of these scenarios.

Execution

There are multiple, mutually-incompatible versions of Python out in the wild.
There are even multiple versions of the packaging tools (like pip). On top
of this, different programs need different packages, often themselves with
mutually-incompatible versions.

Python code typically expects to be installed, and expects to find packages
it depends on installed in a central location. This can be an issue for systems
where we don’t have the permission to install packages/code to a central
location.

All of this makes distributing Python applications quite tricky. It’s common
to use bundling tools like PyInstaller, but
virtualenv is also a popular option [1].

Go is a statically compiled language, so this is a non-problem! Binaries are
easy to build and distribute; the binary is a native executable for a given
platform (just like a native executable built from C or C++ source), and has
no dependencies on compiler or package versions. While you can install Go
programs into a central location, you by no means have to do this. In fact, you
typically don’t have to install Go programs at all. Just invoke the binary.

It’s also worth mentioning that Go has great cross-compilation support, making
it easy to create binaries for multiple OSes from a single development machine.

Development

Consider the following situation: you’re developing a package, which depends on
N other packages at specific versions; e.g. you need package foo at version
1.2 or above. Your system may have an older version of foo installed – 0.9;
you try to upgrade it to 1.2 and some other program breaks. Now, this all sounds
very manageable for package foo – how hard can it be to upgrade the uses of
this simple package?

Reality is more difficult. foo could be Django; your code depends on
a new version, while some other critical systems depend on an old version. Good
luck fixing this conundrum. In Python, viruatenv is a critical tool to make
such situations manageable; newer tools like pipenv wrap virtualenv with more usability
patterns.

How about Go?

If you’re using Go modules, this situation is very easy to handle. In a way,
a Go module serves as its own virtualenv. Your go.mod file specifies the
exact versions of dependency packages needed for your development, and these
versions don’t mix up with packages you need to develop some other project
(which has its own go.mod).

Moreover, Go module directives like replace make it easy to short-circuit
dependencies to try local patches. While debugging your project you find that
package foo has a bug that may be affecting you? Want to try a quick fix and
see if you’re right? No problem, just clone foo locally, apply a fix, and
use a replace to use this locally patched foo. Tools like gohack automate this process in a very
convenient way.

What about different Go versions? Suppose you have to investigate a user report
complaining that your code doesn’t work with an older Go version. Or maybe
you’re curious to see how the upcoming beta release of a Go version will affect
you. Go makes it easy to install different versions locally. These different versions have
their own standard libraries that won’t interfere with each other.


[1] Fun fact: this blog uses
the Pelican static site generator. To regenerate the site I run Pelican
in a virtualenv because I need a specific version of Pelican with some
personal patches.