Cross-Platform Go Libraries: Options, Pitfalls, and Conclusions

After months of struggling with this problem — and spending today running yet another round of experiments — I finally came to a conclusion: there’s no clean way to build Go libraries that can be reliably consumed from other languages across all major platforms (Windows, Linux, macOS, Android, and iOS).

Long story short: I came to the conclusion that you should never create C wrappers for your Go libraries or distribute them as C-compatible libraries. It creates more headaches than it solves.

TL;DR

The initial idea was to write some functionality in Go that could then be reused by developers working in different languages and on different platforms. Here’s what I learned:

  1. Distributing .a static libraries This works fine—until a developer tries to link against another .a Go library in the same project. At that point, it falls apart: you can’t link two Go static libraries into the same executable because both carry their own Go runtime with identical symbols, and the linker will fail with conflicts. Some linkers have flags that allow you to “pick the first symbol and ignore duplicates,” but the Go team explicitly warns against this. It might compile, but it will almost certainly break in unpredictable ways.

  2. Using .so shared libraries Building a Go library as a .so file and linking it into a C project generally works — even if your project needs multiple Go libraries. That’s because each .so is a separate dynamic object with its own isolated Go runtime, and the dynamic linker can load more than one at runtime without symbol collisions.

    However, this is not officially supported by the Go team. Even though .so isolation usually prevents duplicate-symbol issues (unlike static .a archives), developers have reported many subtle runtime problems: memory corruption, crashes in the Go scheduler, deadlocks, and random panics that are hard to reproduce. The official stance is: “it may work, but don’t rely on it.”

  3. Wrapping your library in a separate process Another “bold” idea is to run your Go code in its own process and provide a C wrapper that communicates with it via IPC. Problem solved? Not really. iOS apps can’t just spin up arbitrary external processes and talk to them. On Android it’s possible, but tricky to get right. Bottom line: this approach is not truly cross-platform.

The Reality

A complete, portable solution either doesn’t exist or I haven’t found it.

If you need to use multiple Go libraries inside one C project, the only viable option is to create a single umbrella Go project. That project pulls in all the Go libraries you need as dependencies and exports a unified superset of C functions. That way, you’re linking only one Go runtime into the final binary.

My Conclusion

If you’re writing a Go library, the only people who can really use it effectively are other Go developers.

If you need to embed a Go library into, say, an iOS app, don’t put a C wrapper inside the library itself—that will only add complexity and frustration. Instead, the developer who wants to use your Go library (maybe it’s you) should create a separate Go project that acts as an umbrella. This project pulls in all the required Go libraries as dependencies and exposes a single set of C-exported functions for the application to consume. That way, the integration point is clean, and each app can decide exactly which Go libraries to bundle.

To make this easier for others, you can provide a reference Go file that enumerates the exported functions with //export annotations. That way, anyone who wants to use your library can simply copy this file into their own umbrella project and immediately get a working integration — without forcing every Go library to ship with its own wrapper.