I <3 Build Tags

A Primer

Go is a safe language. What this means is it aims to prevent several popular methods of footgun that have plagued many popular unsafe languages—like C.

C doesn’t care if you write past the end of an array. Your array only has 30 elements? Cool. C doesn’t care. It will happily write from offset zero until the end of the address space (or until the kernel has enough of it and segfaults). That’s what happens when your entire language is pretty much only a memory address and a type name.

Go, on the other hand, inserts bounds checks on all array (and slice) accesses it can’t prove are safe. (Which is currently under development. Go 1.7 should see some improvements on bounds-check elimination.)

C also loves to convert integers without telling you, and has a lot of fun rules that dictate these conversions. Sometimes this is great, other times you stumble into undefined behavior and your program beats you up and steals your lunch money.

Go doesn’t do that. In fact, Go’s typing is rather strict: const a = int32(12) + int64(30) is illegal, even though the compiler can prove that nothing silly will happen.

Let's Be Unsafe

All this goes out the window, however, when the unsafe package is used. The unsafe package is, quite obviously, unsafe—it allows you to skirt Go’s type safety and do pretty much whatever you please.

With the unsafe package you can write arbitrary memory, convert between types, and wreck general havoc. It’s a lot of fun. But it’s also dangerous.

The best way to use the unsafe package is to not use it at all.
– Me

Yeah. I just quoted myself. But it’s true. If you find yourself wanting to use unsafe, you should really try to find another solution.

Since the unsafe package isn’t bound by the Go 1 compatibility guarantee, it’s free to change whenever. That, in addition to the poor documentation (granted, release 1.6 did add better documentation) and the multitude of ways you can easily mess up means the unsafe package is best left for the standard library or making syscalls.

The issue isn’t whether your code works or not (which it very well could), but whether it’ll work in release 1.7. Or 1.8. Or 1.9. You get the picture.

...But Let's Do It Safely

The other day I was writing a little bloom filter, and once I was finished I Googled around for other Go implementations so I could compare libraries.

I noticed that one of the leading search results makes liberal use of unsafe.

Okay. Whatever. A cursory glance seemed to indicate the unsafe code was written properly and the bloom filter worked well. But there’s no real indication that the code is inherently unsafe. Yes, reading the entire source would blatantly tell you Hey, this is unsafe, but if you’re like me, your usual method of choosing packages usually goes something like this:

  1. Google for X
  2. View X on godoc.org
  3. Glance at the API to make sure it looks sane
  4. Glance at the code to make sure it looks sane
  5. Use it

It’s easy to miss unsafe code with that Super Simple(TM) five-step method. What about those who don’t skip steps three and four? They’re guaranteed to unknowingly use unsafe code

A lot of new Go programmers come from languages like JavaScript; in particular, those who use Node seem to graviate to Go. The Node community has some problems when it comes to vendoring in code. Ignoring the insanity of importing eleven lines of code, a lot of programs make liberal use of imports—do you really think they read the entire source code of every import? No. I’m a sample size of one, but I know if I had forty imports I sure wouldn’t read the entirety of all of them.

If that behavior continues on into Go (which it probably does), it’s very reasonable a new Go user could run unsafe code and be none the wiser.

To be fair, the aforementioned bloom filter did mention in its README it used the unsafe package, but that’s beside the point—t’s too easy to miss.

Don’t worry. I didn’t just complain without providing a solution for the problem.

The Solution

Build tags.

Assuming you…

  1. Have a legitimate reason to use package unsafe
  2. Wrote a safe version first
  3. Wrote proper unsafe code
  4. Have tested it
  5. Have atoned for your sins

…before you release your code into the wild, use build tags to provide two separate versions of your code.

Using build tags to separate your unsafe code from your normal, safe code is straightforward

Create two files (touch foo.go && foo_unsafe.go) and write your safe Go code inside foo.go and the unsafe code inside foo_unsafe.go.

Once you’ve done that, add the unsafe build tag to the beginning of the unsafe file (// +build unsafe—don’t forget to add a blank line between the build tag line and your package declaration line) and // +build !unsafe to the beginning of the safe file.

Boom! All done. Now those who want to use your unsafe code can add a build tag go install --tags=unsafe github.com/you/your-cool-package and use all the gross unsafe code they want, while the normal users and new Go programmers won’t use unsafe code by default.

Super simple.


Yes, I use unsafe from time to time. I really do believe that using build tags as a switch for unsafe code is the only safe way to write unsafe code. (Assuming you’re not using it for syscalls and whatnot.)

At the very least you won’t leave some new Go programmer scratching her head when her code breaks because your old package was unsafe by default. Or you won’t cause some other new Go programmer to learn bad habits and then yelled^H^H^H politely told why his liberal use of unsafe is wrong.

Please be safe.

| |

comments powered by Disqus