Linking Zig from Haskell

Posted on September 12, 2025

I recently published the Haskell tigerbeetle-hs client library for the Tigerbeetle database.

In order to develop client libraries for Tigerbeetle it is recommended to call the official C client library instead of trying to implement the wire protocol yourself. This client happens to be written in Zig which can generate code that is compatible with the calling conventions of many different platforms. It just looks like a regular ELF shared object file, libtb_client.so.

So it should be as simple as using Haskell’s FFI, right?

This post is about a particular issue I ran into while calling libtb_client.so (from Tigerbeetle 0.16.33) from Haskell.

The Problem

I could compile the tigerbeetle-hs library. If you tried to run the test program however you would get a segmentation fault. And an error like this:

Relink `/home/agentultra/Projects/tigerbeetle-hs/lib/x86_64-linux-gnu.2.27/libtb_client.so' with `/nix/store/rmy663w9p7xb202rcln4jjzmvivznmz8-glibc-2.40-66/lib/libm.so.6' for IFUNC symbol `sin'

Symbol Tables and Object Files and Linkers, Oh My!

That error is coming from the Linux run-time linker in the depths of /lib/ld-linux.so.2. It is trying to tell us that the program that is being loaded already has a function symbol for sin when it tries to link libtb_client.so.

An object file, on most Unix/Linux-like systems, is a binary file formatted following the ELF standard. When we compile some code to make an executable application or share some library code we get a binary file filled with data. ELF organizes it in a standard way so that programs know where to find things. Depending on your target platform you may get binary data in a different format.

First let’s find out where this symbol is defined and what it’s defined as, we can see what libtb_client.so dynamically links against:

> ldd src/clients/c/lib/x86_64-linux-gnu.2.27/libtb_client.so
    linux-vdso.so.1 (0x00007ffff7fc4000)
    libpthread.so.0 => /nix/store/h7zcxabfxa7v5xdna45y2hplj31ncf8a-glibc-2.40-36/lib/libpthread.so.0 (0x00007ffff7e7d000)
    libc.so.6 => /nix/store/h7zcxabfxa7v5xdna45y2hplj31ncf8a-glibc-2.40-36/lib/libc.so.6 (0x00007ffff7c78000)
    /nix/store/h7zcxabfxa7v5xdna45y2hplj31ncf8a-glibc-2.40-36/lib64/ld-linux-x86-64.so.2 (0x00007ffff7fc6000)

Hm… does libtb_client.so contain it’s own definition for sin?

> readelf -s src/clients/c/lib/x86_64-linux-gnu.2.27/libtb_client.so | grep sin
Num:   Value           Size Type    Bind   Vis      Ndx Name
...
315: 000000000012db20   725 FUNC    WEAK   DEFAULT   12 sin
...

Seems it does. And it’s binding is WEAK.

Name Clashes and Relinking

It turns out that the Haskell toolchain will link -lm first when invoking the platform linker. This means our executable will have the sin symbol from libm through its dependency.

So what happens when our executable file already has a symbol for sin and a library we are trying to load dynamically also has a symbol called sin in its symbol table?

Why Does Tigerbeetle Define sin?

It doesn’t!

It turns out that the Zig build toolchain, as of 0.13.0 or so, if not asked to link against libc will provide its own symbols that clash with ones defined by libc as it generates ELF files.

The Fix

One way we fixed the issue in development was to patch the ELF header of libtb_client.so so that it advertises that it depends on libm. The dynamic linker is then able to resolve the symbol for sin by following the dependencies to the executable.

The real fix is for Zig to mark these symbols as HIDDEN so that they do not interfere with libraries that link to libc… which is already on Zig master as of this writing.

The Lesson

Whatever your thoughts and feelings are about libc the fact remains that many build tools and ecosystems depend on it for better or worse. If you’re providing libraries for users to link against then you should probably assume they’re linking libc. Avoid clashing with symbols defined in libc!

Thanks to the wonderful Tigerbeetle folks and to jkachmar for working together and helping resolve this interesting issue.

Because of our work we’re able to link to libraries compiled from Zig code from Haskell!