For reasons1 I need to embed a Rust binary in another Rust binary, however out of the box Cargo doesn’t support this easily. RFC 3028 will fix this but as of Rust
1.52 it is not implemented. This blog post will go though an example, show where Cargo does not work today and how to overcome this with a custom script.
A basic example would be having two crates,
outer in the same Cargo workspace. Both are binary crates, but we want to embed the entire
inner binary into the
outer binary. An example for this would be that when
outer starts up, it needs to extract the binary in a special location and ask another application to launch
A naive approach
A Cargo workspace could look like:
At the root we have our
Cargo.toml which declares the two crates:
Cargo.toml we have:
And the entire code for the crate is:
outer crate could look like
In the above approach, we declare a dependency from
inner and hard code the location of the debug binary.
From a clean workspace a build doesn’t work
However a subsequent build does work:
And we can run
There are three problems here:
- Cargo does not respect the dependency from
innerand proceeds to build them in parallel.
- Subsequent builds ‘work’ but you might be getting a stale binary embedded which can lead to a lot of confusion.
- There is no easy way for
outerto know were the desired
innerbinary is. The above code assumes a debug build for the current architecture but the path would be different if it were a release build or a different architecture.
A custom build script
The only way I have found to do this is to have a custom
build.rs build script for
outer which expects the desired binary in a specific place, and another script to coordinate the builds of
One benefit of this approach is that we can get rid of the dependency between
outer which will prevent us from accidentally referencing code in another binary.
outer looks like:
We have to use environment variables because that’s the only way a build script can get an argument.
Once this is done we can change
./outer/src/main.rs to read from
After this is done we just need a script to drive cargo and set the appropriate environment variables:
With this script, we explicitly build
inner first, grab the output location from cargo and persist it in the environment variable that the
Invoking the script allows us to properly build
inner and then embed it in
outer. The design of the shell script allows us to also pass arguments to
cargo build like
--release or a target architecture. This script also works from a clean build and will always ensure we are embedding the latest build of
Consider the case of trying to bundle a complex application into a single binary. This application could extract it’s own sub applications on startup to simplify distribution.↩︎