osa1 github gitlab twitter cv rss

Conditional compilation based on crate type

December 24, 2020 - Tagged as: en, rust.

Suppose you have a no_std crate that you want to use in two ways:

  1. As a self-contained static library, to link with other (non-Rust) code
  2. As a Rust library, to import from another crate to test it

(1) is the main use case for this library. (2) is because you want to test this library and you want to be able to use Rust’s std and other Rust libraries for testing.

The Rust crate type for (1) is staticlib. For (2) you need rlib. (documentation on crate types)

Here’s the problem. To be able to generate staticlib you need to implement a panic handler as otherwise the code won’t know how to panic1. However, if you define a panic handler, you won’t be able to use your crate in other crates anymore as your panic handler will clash with the std panic handler.

4 files needed to demonstrate this:

-- Cargo.toml for the library
[package]
name = "nostd_lib"
version = "0.1.0"
authors = []
edition = "2018"

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

[profile.dev]
panic = "abort"

[profile.release]
panic = "abort"

-- lib.rs
#![no_std]

#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! {
    loop {}
}

-- Cargo.toml for the importing crate
[package]
name = "nostd_bin"
version = "0.1.0"
authors = []
edition = "2018"

[dependencies]
nostd_lib = { path = "../nostd_lib" }

-- main.rs
extern crate nostd_lib;

fn main() {}

The library builds fine, but if you try to build nostd_bin you’ll get this error:

error: duplicate lang item in crate `nostd_lib` (which `nostd_bin` depends on): `panic_impl`.
  |
  = note: the lang item is first defined in crate `std` (which `nostd_bin` depends on)
  = note: first definition in `std` loaded from ...
  = note: second definition in `nostd_lib` loaded from ...

Which says you now have two panic handlers: one in std and one in your library.

If you remove the panic handler in the library then you won’t be able to build the library anymore:

error: `#[panic_handler]` function required, but not found

So you need some kind of conditional compilation, to generate panic handler only when generating staticlib. Unfortunately conditional compilation based on crate type is currently not possible. It is also not possible to specify target crate type when invoking cargo.

The least hacky way I could find to solve this (and without using anything other than just cargo build to build) is by having two Cargo.toml files.

Cargo really wants manifest files to be named Cargo.toml, so we put the files in different directories. In my case the top-level one is for staticlib and it looks like this:

[package]
name = "nostd_lib"
version = "0.1.0"
authors = []
edition = "2018"

[features]
default = ["panic_handler"]
panic_handler = []

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

[profile.dev]
panic = "abort"

[profile.release]
panic = "abort"

I also update lib.rs to only define the panic handler when the feature is enabled:

#[cfg(feature = "panic_handler")]
#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! {
    ...
}

Now I can build the library at the library’s top-level with just cargo build. Because the panic_handler feature is enabled by default in this Cargo.toml, the panic handler will be defined by default with just cargo build and static library will build and work fine.

For the rlib I create a similar Cargo.toml in rlib directory:

[package]
name = "nostd_lib"
version = "0.1.0"
authors = []
edition = "2018"

[lib]
crate-type = ["rlib"]
path = "../src/lib.rs"

[profile.dev]
panic = "abort"

[profile.release]
panic = "abort"

The differences are: this one only generates rlib, doesn’t define the panic_handler feature, and specifies the library source path explicitly (as it’s not in the default location relative to this Cargo.toml). It’s fine to refer to a feature that you never define in Cargo.toml in your code, so lib.rs is still fine, and the panic handler will never be built when you build the crate with this Cargo.toml.

Now in the importing crate I use this Cargo.toml instead of the top-level one:

[dependencies]
nostd_lib = { path = "../nostd_lib/rlib" }

And it works fine. The downside is I have two Cargo.toml files now, but in my case that’s not a big deal, as my Cargo.toml is quite small and have no dependencies other than libc2.

I hope this is helpful. If you know any better way to do conditional compilation based on crate types, or to solve the problem of generating usable staticlib and rlibs from a single no_std crate, let me know!


  1. You need a panic_handler even if you never panic in your crate (assuming that’s possible). For example, you can’t compile fn main() {} with no_std, panic=abort, and without a panic_handler: the compiler complains about the missing panic handler.↩︎

  2. If you’re working on a no_std crate I think you won’t be able to find a lot of libraries that you can use anyway.↩︎