December 24, 2020 - Tagged as: en, rust.
Suppose you have a no_std
crate that you want to use in two ways:
(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 libc
2.
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 rlib
s from a single no_std
crate, let me know!
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.↩︎
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.↩︎