Skip to content

Top 5 reasons to use bindings

Maxim Kupriianov edited this page Aug 29, 2017 · 1 revision

This article describes the rationale why anybody would use bindings instead of writing a pure-Go package doing the same functionality.

Note, that previously CGo had varying status relatively to the Go tools and runtime, there was a period of time (< Go 1.5) when the runtime was in C and using CGo could provide a significant performance boost, at the cost of safety. Many people including Go evangelists would advise against using CGo and that was reasonably fair.

But since Go 1.6 release with introduction of pointer passing rules CGo has stopped looking just like an ugly hack and started to be a strong and dedicated feature of Go. Between Go 1.6 and Go 1.7 many CGo issues that previously would end up in "wontfix" stage were addressed and fixed respectively (e.g. #13830).

I. The heritage

One of the most important things that many of us can easily forget in discussions - C/C++ has a huge heritage. I'm pretty sure that today's technology is partly based on things developed between 90s through 2016, and varying from domain to domain, this dependency strengthens. How would anyone rewrite the Qt Framework? And how about all those audio codecs like ALAC or Vorbis? Okay, the latter two are easy to rewrite, but how about libvpx from the WebM project then?

Among those that are really hard to rewrite, there are many for which there is nothing to rewrite at all. Consider native adapters for various system drivers, something like PortAudio or PortMIDI that are meant to be portable and support dozens of OSes and environment configurations.

And finally, if some good C library is worth a pure-Go rewrite, consider getting a quick binding set, write your tests and then rewrite the library piece by piece, as if it was the ship of Theseus and validate your pure code on each iteration that nothing has been broken with the changes.

II. Modern APIs

Putting aside the heritage which can be a really legacy code that nobody ever would touch again, there are plenty of C libraries that are created nowadays for the future. They are designed to be as much portable as possible, embeddable, binding-ready, and usually have very clean and reentrant APIs. For a good explanation see Why C? paragraph of the libpostal C library documentation.

Moreover, some modern APIs are being auto-generated from language-agnostic XML specs like Vulkan Graphics API and doing bindings for them by hand feels like fighting the windmills. Also, there is nothing to "rewrite" in Go, because this API provides no implementation in user space either.

And there is one more category of modern libs, they are something like pimping your ride: they provide clean and reentrant wrappers for old heritage libs, consider an example of libpd. Nobody will be ever able to "rewrite" the wrapper because the main action goes in the heritage code that acts like an anchor. And if this thin wrapper allows you to run that code on Android/iOS platforms, why not bring that into Go? With c-for-go that was a weekend project made with joy.

III. Avoiding roadblocks

Now about the business and the technology stack. Imagine there is a serious project: dozens of modules, months of development planned in roadmap and now it's time to choose the stack. And now imagine that everyone in your team love Go, but since there are 3 external dependencies in C/C++ your entire project will be done in C/C++ too. So you'll be spending 50% of your productive time catching some pesky bugs with memory accesses and invalid cast magic, wishing you was writing a perfect system with channels and goroutines in Go instead. No way.

This project brings the bindings for almost any C module ever created, even in C++ if there is a C wrapper for it. At no cost you may ignore the fact that some pieces of your app in fact are written in C and work with them as if they were some native Go packages, while having 90% of your app logic in pure Go. No more spontaneous roadblocks on the project roadmap.

IV. Learning things

This is the tricky one, I discovered the effect while preparing examples for c-for-go and testing the resulting bindings. Let's look for example on a simple library for Ogg/Vorbis decoding.

At first, you study its files and the method set, considering the style the code is written in, thinking how your idiomatic wrapper would look like. Then you write a c-for-go manifest in YAML, there you make yourself familiar with naming conventions and method signatures. For the latter, you must decide whether there is a slice argument or a pointer to a single object. You start reading docs. When the bindings are finally generated, you get quite a human-readable piece of code, but it's still a low-level API with taste of C.

Then you want to create an example in Go, that will use the generated package, however the API is too low-level in terms of the application domain too, there is no just "play dat shit" method in this kind of libraries. So I wrote the decoder package that operates on byte streams and produces streams of samples. I must say that was a hell of experience, now I'm perfectly aware how this codec works.

The most important thing here is that while creating the decoder package there was no C code touching at all. I was reading the original documentation and was working with the library as if it has been written in pure Go. Almost the same thing with the Vulkan Graphics API: there are almost 4000 lines of code transcribed from C to Go in demos and they simply work. The same API, the same docs, you learn the logic and rationale between them all and don't bother about weird C magic somebody used back to stone age of software engineering.

V. Performance improving

And the latest but not the least reason in this list: falling back to C may dramatically increase the performance. Since 1.5 there are significant penalties for calling CGo too frequent, however the amount of calls needed to get the job done vary from domain to domain. Some C libraries maxed out the amount of tricks used to increase performance like custom allocators, zero-copy buffers, very efficient data types and so on. All this goes under the hood, making the library look like an iceberg: you just use the tip. And no GC pressure at all. This is particularly practical for embedded key-value stores or databases like LMDB which is about 3x-10x faster than BoltDB.

However, I strictly advise to always profile your code, even when efficiency may seem obvious. With the new SSA additions and runtime improvements Go may perform even better than C, especially with all these not-so-thin CGo wrappers and validators around the C API.