Why is the Rust libstd linked dynamically without using -C prefer-dynamic flag after compiling a dynamic library?

Viewed 2308

I am having an issue understanding how Cargo links binaries when building a dynamic library. To understand it better, I took the coreutils project and tried to build 'ls' changing Cargo.toml and other stuff to make the executable as small as possible. I rapidly found out that linking libraries dynamically could reduce the size of the executables drastically.

Digging through the internet for optimizations, I found that when you generate libraries as "dylibs" in Cargo, they still include "libstd" statically unless you explicitly tell the compiler to include it dynamically using the flag "-C prefer-dynamic".

I don't use the flag, but still end up having libstd linked dynamically, which is very strange.

Cargo.toml for ls

I modified this a bit.

[package]
name = "ls"
version = "0.0.1"
authors = ["Jeremiah Peschka <jeremiah.peschka@gmail.com>"]



[lib]
crate-type=["dylib"]
name = "uu_ls"
path = "ls.rs"

[dependencies]
getopts = "0.2.14"
pretty-bytes = "0.2.1"
term_grid = "0.1.5"
termsize = "0.1.4"
time = "0.1.38"
lazy_static = "0.2.8"
unicode-width = "0.1.4"

[dependencies.uucore]
path = "../uucore"
default-features = false
features = ["entries"]

[[bin]]
name = "ls"
path = "main.rs"

Cargo.toml of uucore

This is the only dependency of ls that I need to compile:

[package]
name = "uucore"
version = "0.0.1"
authors = []


[dependencies]
getopts = "0.2.14"
time = { version = "0.1.38", optional = true }
data-encoding = { version = "^1.1", optional = true }

[dependencies.libc]
git = "https://github.com/rust-lang/libc.git"
optional = true

[features]
fs = ["libc"]
utf8 = []
encoding = ["data-encoding"]
parse_time = []
utmpx = ["time", "libc"]
process = ["libc"]
signals = []
entries = ["libc"]
wide = []
utsname = ["libc"]
default = ["fs", "libc", "utf8", "utsname", "encoding", "parse_time", "utmpx", "process", "entries", "signals", "wide"]

[lib]
crate-type=["dylib"]
path = "lib.rs"
name = "uucore"

After building the library, I determine whether if it is linked dynamically:

ldd ~/target/release/libuu_ls.so 
    linux-vdso.so.1 =>  (0x00007ffd96b98000)
    libuucore.so => not found
    libstd-8eeda694d27cc3ba.so => /home/cibo/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/libstd-8eeda694d27cc3ba.so (0x00007f86a29d0000)
    librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f86a27c8000)
    libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f86a25ab000)
    libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f86a2394000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f86a1fca000)
    libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f86a1cc1000)
    libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f86a1abc000)
    /lib64/ld-linux-x86-64.so.2 (0x0000558ebb132000)

As you can see, libstd is dynamically linked here.

1 Answers

According to https://github.com/rust-lang/rust/blob/8961616e6004e204327f575081509b049268762b/compiler/rustc_metadata/src/dependency_format.rs, the algorithm for selecting between static and dynamic libraries is:

//! 1. If static linking is required, then require all upstream dependencies
//!    to be available as rlibs. If not, generate an error.
//! 2. If static linking is requested (generating an executable), then
//!    attempt to use all upstream dependencies as rlibs. If any are not
//!    found, bail out and continue to step 3.
//! 3. Static linking has failed, at least one library must be dynamically
//!    linked. Apply a heuristic by greedily maximizing the number of
//!    dynamically linked libraries.
//! 4. Each upstream dependency available as a dynamic library is
//!    registered. The dependencies all propagate, adding to a map. It is
//!    possible for a dylib to add a static library as a dependency, but it
//!    is illegal for two dylibs to add the same static library as a
//!    dependency. The same dylib can be added twice. Additionally, it is
//!    illegal to add a static dependency when it was previously found as a
//!    dylib (and vice versa)
//! 5. After all dynamic dependencies have been traversed, re-traverse the
//!    remaining dependencies and add them statically (if they haven't been
//!    added already).

According to this algorithm, if not all libraries can be found as static, rustc will automatically prefer dynamic libraries. Furthermore,

//! Currently, there is no way to specify the preference of linkage with a
//! particular library (other than a global dynamic/static switch).

In your case, it is impossible regardless to link statically against libstd because libuucore.so does not contain libstd. It may be possible on ELF platforms to treat libuucore.so as a "plugin" with symbols satisfied by the main executable, but this is not a standard configuration.

Related