Rust and macOS compilation / linking issue

Back in mid-2021 I installed Rust on my personal old (2014) MacBook Pro (Big Sur) using Rustup, which apparently went fine. When trying to actually build or compile any rust however, I immediately got stuck. I hadn't had any issues installing or using Rust on any other Macbooks.

Compiling a minimal 'Hello World' by rustc main.rs resulted in the following (initially) unapproachable error:

➜  ~ rustc main.rs
error: linking with `cc` failed: exit status: 1
  |
  = note: "cc" "-m64" "-arch" "x86_64" "main.main.cbc7ddee-cgu.0.rcgu.o" "main.main.cbc7ddee-cgu.1.rcgu.o" "main.main.cbc7ddee-cgu.2.rcgu.o" "main.main.cbc7ddee-cgu.3.rcgu.o" "main.main.cbc7ddee-cgu.4.rcgu.o" "main.main.cbc7ddee-cgu.5.rcgu.o" "main.main.cbc7ddee-cgu.6.rcgu.o" "main.main.cbc7ddee-cgu.7.rcgu.o" "main.4kqwyhdgeomtzpxx.rcgu.o" "-L" "/Users/username/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/x86_64-apple-darwin/lib" "/Users/username/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/x86_64-apple-darwin/lib/libstd-91f0e69a7ebd6e00.rlib" "/Users/username/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/x86_64-apple-darwin/lib/libpanic_unwind-36050f194975374d.rlib" "/Users/username/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/x86_64-apple-darwin/lib/libobject-e31c8111c698d163.rlib" "/Users/username/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/x86_64-apple-darwin/lib/libmemchr-cadc9c82d273f933.rlib" "/Users/username/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/x86_64-apple-darwin/lib/libaddr2line-e4462cb73bc7c44d.rlib" "/Users/username/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/x86_64-apple-darwin/lib/libgimli-37047837b923d0f0.rlib" "/Users/username/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/x86_64-apple-darwin/lib/libstd_detect-16528d37a8f3eab3.rlib" "/Users/username/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/x86_64-apple-darwin/lib/librustc_demangle-54ab9662306ce2de.rlib" "/Users/username/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/x86_64-apple-darwin/lib/libhashbrown-b493ccc9a0b06169.rlib" "/Users/username/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/x86_64-apple-darwin/lib/librustc_std_workspace_alloc-7a86533803c69aa4.rlib" "/Users/username/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/x86_64-apple-darwin/lib/libunwind-95847f4481c1d402.rlib" "/Users/username/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/x86_64-apple-darwin/lib/libcfg_if-f9d3de4d6bdbf64b.rlib" "/Users/username/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/x86_64-apple-darwin/lib/liblibc-b524a7407cf68766.rlib" "/Users/username/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/x86_64-apple-darwin/lib/liballoc-38142aee14781386.rlib" "/Users/username/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/x86_64-apple-darwin/lib/librustc_std_workspace_core-c27d31671763ac4a.rlib" "/Users/username/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/x86_64-apple-darwin/lib/libcore-7eec28d9e637d9ae.rlib" "/Users/username/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/x86_64-apple-darwin/lib/libcompiler_builtins-870fc7f82e4bde6f.rlib" "-lSystem" "-lresolv" "-lc" "-lm" "-liconv" "-L" "/Users/username/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/x86_64-apple-darwin/lib" "-o" "main" "-Wl,-dead_strip" "-nodefaultlibs"
  = note: ld: library not found for -lSystem
          clang: error: linker command failed with exit code 1 (use -v to see invocation)

error: aborting due to previous error

(My terminal displayed all of this wrapped, so it didn't look anywhere near as nice and clean as presented here...!)

The internet suggested ensuring that I had XCode developer tools installed (I did) and I even installed the full 12gb XCode setup just in case, to no avail. I spent ages faffing around trying to swap out the (often problematic and out of date) built-in Apple versions of clang / gcc / ld and the like for the ones in homebrew, but no dice there either.

After much googling, I found someone else had this same issue in Jan 2022 on the Rust forums, working out that the key issue here was ld: library not found for -lSystem. It also (wonderfully) included a suggested fix: to pass a new (random and otherwise unknowable) library search path into rustc when calling it:

➜  ~ rustc -L /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib ./main.rs

But this is clunky, and not massively helpful if you have to do this every time. I don't want to have to setup a build script to inject it. I also had no idea where this came from directly, so it doesn't help with the 'why'.

The immediate fix for this is to add this directory to both your path and your library path, i.e. adding the following to my .zshrc (or your .bashrc or similar):

export PATH=$PATH:/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib # Needed for Rust compilation and linking
export LIBRARY_PATH=$LIBRARY_PATH:/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib # Needed for Rust compilation and linking

After discussing with a couple of friends (thanks Adam and Jamie), we worked out that Rust was looking for the System library, usually found at /usr/lib/libSystem.dylib — but such a file doesn't exist. This appears to be one of the more important Darwin / macOS libraries, so it was weird that we couldn't find or access it. Our conclusion was that my particular install of macOS (having gone through various OS upgrades over the years) has got its shared libraries mucked up somehow, in Apple's move to virtual libraries (moving shared libraries out of the file system and into some sort of virtual registry, aiui), similar to what's described here. Hence, the linker throwing a hissy fit for something that should have been trivial.

Funnily enough, checking which libraries a binary links to using otool gives the following output:

➜  ~ otool -L main
main:
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1311.0.0)
    /usr/lib/libresolv.9.dylib (compatibility version 1.0.0, current version 1.0.0)
    /usr/lib/libiconv.2.dylib (compatibility version 7.0.0, current version 7.0.0)

...none of which exist in actual or symlink form in /usr/lib/, but the program runs just fine.

Still no idea how to fix more permanently, or to resolve the non-existence of /usr/lib/libSystem.dylib in a more sensible way.

Hope this saves someone else (or future Martin) from some pain!