How do I split my code into library and binary parts when the library is a `staticlib`?

The following compiles fine:

[package]
name = "foo"
version = "0.1.0"
edition = "2024"

[dependencies]

[lib]
name = "foo"
path = "lib.rs"

[[bin]]
name = "bar"
path = "main.rs"
// main.rs
use foo::test;

fn main () {
    test();
    println!("Yay!");
}
// lib.rs
pub fn test() {}

However, when I add the key crate-type = ["staticlib"] to the [lib] table in Cargo.toml, I get:

error[E0432]: unresolved import `foo`
 --> main.rs:1:5
  |
1 | use foo::test;
  |     ^^^ use of undeclared crate or module `foo`

For more information about this error, try `rustc --explain E0432`.
error: could not compile `foo` (bin "bar") due to 1 previous error
warning: build failed, waiting for other jobs to finish...

Why exactly is Cargo getting upset here? I'd expect not much to be different at the codegen stage, and when linking it could still find the necessary objects in the static library. Then again, I'm nowhere near an expert on Rust.

1 Like

Just a guess, but since you're linking with a Rust binary as well as producing a static lib, I'd try:

crate-type = ["lib", "staticlib"]
1 Like

Sure, that works. Using a static lib is part of a design choice that I should probably rethink... (Edit: It seems LTO achieves what I was going for.) I'm still curious why the original didn't work.

1 Like

When you create a normal Rust executable, only the symbols actually used will be linked in. Is that not what you were seeing?

"staticlib" and "cdylib" are for linking with non-Rust apps. See:
https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-crate-type-field
https://doc.rust-lang.org/reference/linkage.html#r-link.staticlib

For LTO beyond just dead code removal (which always happens), see:
https://doc.rust-lang.org/cargo/reference/profiles.html#lto

1 Like

When you specified crate-type = ["staticlib"] you override the default of lib, which is what allows the library to be usable in other Rust crates, like your main. Specifying lib along with staticlib makes it available again.

1 Like

Naming of these things is such a mess.

Library crates in Rust are also static libraries. The lib crate type that generates an .rlib file is a static library. It's almost the same thing as .a static library, with just extra metadata for Rust.

The staticlib crate type is also a static library, but intentionally not used for Rust. It's meant for C only, so when it's not used in your Rust binary that's not a bug or limitation, but the main purpose of this type.

And static libraries are very different from standalone/redistributable .so/.dll libraries (which Rust calls cdylib).

Static libraries are just a bunch of object files saved together, but they're not a standalone thing, but more like temporary files of an unfinished compilation. They still need linking, and implicitly depend on the build settings and everything else that will be added at the linking stage. So in either case it's the binary build that does the linking, and doesn't depend on the static library after linking.

Yeah, something like "solib" or even "dll" instead of "cdylib" would have been more explicit, and maybe "alib" for "staticlib". But we're stuck with what we have.

"dynamic library" is a common "generic" name for .so/.dll, so contracting it to dylib and adding a c prefix for the c-abi version is sensible enough. In contrast against lib / staticlib for the rust and c-abi versions of static linking it looks positively self-documenting. I assume it would be too much trouble to rename staticlib to clib at this point.

1 Like