/

/

Shipping closed-source libraries built with Rust

/

/

Shipping closed-source libraries built with Rust

Shipping closed-source libraries built with Rust

Shipping closed-source libraries built with Rust

Written by

Stephan Eckes

,

Founding Engineer

Research

/

Stephan Eckes

Written by

,

Founding Engineer

Research

/

Rust is a good fit for commercial libraries, but shipping those libraries as precompiled static binaries can be awkward.

Distributing a Rust library as a precompiled static binary sounds simple until someone tries to link it into their own Rust codebase. At that point you can end up with linker errors, caused by internal Rust and dependency symbols leaking out of your staticlib and colliding with the consumer's own copies. We ran into this shipping our closed-source SDK, and built a small open-source tool, lib-patcher, to fix it for good. Here's what causes the collisions, how we solved it, and a walkthrough for using it in your own project.

The problem

We build on Rust because real-time audio leaves no room for crashes and pauses: Rust gives us memory safety without giving up the timing guarantees the processing path depends on. That's why our core SDK is written in Rust, including the model layers, the surrounding DSP, and the telemetry that talks to our backend. With cbindgen, generating a public C API was straightforward. That API let us integrate the SDK into several other languages while keeping the implementation closed source.

We were really excited when we got a first customer who wanted to use the Rust SDK as well. We wrote a safe Rust wrapper around the C API, tested it, and everything worked. Then we prepared the release. As soon as the wrapper and the core SDK were built with different Rust versions, the linker failed:

We could not ship the integration as planned and had to ask the customer to wait. That was frustrating, especially because the wrapper itself had worked without any problems.

Our research led us to a known Rust issue and to staticlib-fucker, a tool that addresses some of these collisions on Windows. This approach worked in our first tests, but the problem was larger and would keep happening if we didn’t address it.

What started as a small linker fix implemented by a working student turned into a substantial development effort. We extended the approach across platforms and tested it with many SDK versions, target architectures, and dependency combinations. That work became lib-patcher.

The source of linker error

A Rust static library contains more than the functions its author intends to expose. It can also make thousands of symbols from the Rust standard library and bundled dependencies visible to the linker. If a customer links that library into another Rust application, the application brings its own copies of many of the same symbols.

Whether those copies collide depends on the platform, the Rust toolchain version, the linker, and whether the library was built with link-time optimization. Because any one of those can change independently, an integration that links cleanly today can start failing after a routine toolchain upgrade, with no code change on either side.

Rust issue #104707 has tracked the compiler behavior behind this problem since 2022. Alan Wu described a related solution while integrating Rust into Ruby's YJIT, and the Windows-focused staticlib-fucker project provided another practical workaround. But we needed a more flexible patch quickly.

What is lib-patcher and what does it do?

lib-patcher is an open-source command-line tool for patching compiled Rust static libraries, developed by engineers at ai-coustics. It processes a Rust static library after compilation: you give it a prefix for the public API, such as mylib_ and it keeps those symbols available while taking the rest out of the consumer's namespace.

On Linux, macOS, and Android, non-API symbols become local. On Windows, where COFF requires a different approach, they are renamed to prevent collisions. The intended API keeps its original names. The tool runs as a command-line build step or as a Rust library from build.rs. It supports Linux, Windows, macOS, and Android.

lib-patcher does not make arbitrary Rust types binary compatible, and it is not a source protection tool. Its job is narrower: keep the public API public and stop implementation symbols from interfering with the customer's build. Simple but important.

Who is it for?

lib-patcher is useful for a specific distribution model: a company writes a library in Rust, exposes a deliberate C-compatible API, and ships the compiled static library without publishing the product's source code. Customers can then call that API from C or from Rust through FFI without inheriting the library's internal Rust symbol table.

We ran into this because we build in Rust, and more teams are landing in the same place every year: the 2025 State of Rust Survey found 48.8% of companies now report non-trivial Rust use in production, up from 38.7% in 2023. As more closed-source software gets built in Rust, more teams will hit this exact linker error, and it's an avoidable one. We'd rather share the fix than have everyone rediscover it on their own.

How to implement lib-patcher? A short tutorial

Start with a Rust crate that builds a static library. Add this to its Cargo.toml:

[lib]
crate-type = ["staticlib"]
[lib]
crate-type = ["staticlib"]
[lib]
crate-type = ["staticlib"]

Give every function in the public C API the same prefix. In this example, the prefix is mylib_:

#[unsafe(no_mangle)]
pub extern "C" fn mylib_add(a: i32, b: i32) -> i32 {
    a + b
}
#[unsafe(no_mangle)]
pub extern "C" fn mylib_add(a: i32, b: i32) -> i32 {
    a + b
}
#[unsafe(no_mangle)]
pub extern "C" fn mylib_add(a: i32, b: i32) -> i32 {
    a + b
}

Install lib-patcher, then build the library:

cargo install lib-patcher
cargo build --release
cargo install lib-patcher
cargo build --release
cargo install lib-patcher
cargo build --release

Patch the compiled archive. The --keep-prefix argument tells lib-patcher which symbols belong to the public API:

lib-patcher \
  --input target/release/libmylib.a \
  --output target/release/libmylib_patched.a \
  --keep-prefix "mylib_"
lib-patcher \
  --input target/release/libmylib.a \
  --output target/release/libmylib_patched.a \
  --keep-prefix "mylib_"
lib-patcher \
  --input target/release/libmylib.a \
  --output target/release/libmylib_patched.a \
  --keep-prefix "mylib_"

You can inspect the public symbols in the result:

lib-patcher \
  --input target/release/libmylib_patched.a \
  --list
lib-patcher \
  --input target/release/libmylib_patched.a \
  --list
lib-patcher \
  --input target/release/libmylib_patched.a \
  --list

Ship and link libmylib_patched.a instead of the original archive. On Windows, use the corresponding .lib paths. When patching an archive for a different target, also pass its full Rust target triplet with --triplet. The required platform tools and a build.rs integration example are in the project README.

Open-sourcing lib-patcher

We do not want other teams to delay a customer integration because of the same linker error. Shipping a closed-source Rust library should not require every company to build and maintain its own set of platform-specific patching scripts.

If you distribute a proprietary SDK, using lib-patcher does not change how you license your product. The tool is available under the permissive MIT or Apache 2.0 licenses, while the library it processes can remain closed source. Because lib-patcher modifies the binary you ship, you can inspect exactly what it does to that binary.

Opening the tool to more users should also expose cases we have not encountered in our own SDKs. Different archives, dependencies, architectures, and linker versions may reveal more work to do. Those reports and fixes can live in one place instead of being rediscovered in separate build systems.

We hope Rust eventually handles this directly. Until then, lib-patcher provides a tested solution for the common platforms and architectures we use today.

Further reading

Final logo

Bring real-time audio intelligence into your voice AI stack

Bring real-time audio intelligence into your voice AI stack

Bring real-time audio intelligence into your voice AI stack