Rust Cross-Compilation: Build for Your Raspberry Pi Without Touching It π¦π₯§
Rust Cross-Compilation: Build for Your Raspberry Pi Without Touching It π¦π₯§
Real story: I once spent two hours debugging why my Python SDR script ran fine on my laptop but crashed immediately on my Raspberry Pi. The culprit? A library version mismatch. On a computer the size of a credit card. Sitting on a shelf. Connected to an antenna on my roof.
Never again.
Coming from 7 years of Laravel and Node.js, "deploying to a different machine" always meant fighting environment differences. Different PHP versions. Different Node versions. Missing native extensions. The ancient ritual of npm install taking twelve minutes on a Pi 3 while you watch the CPU fan struggle.
Rust has a different answer. And it's beautiful.
What Is Cross-Compilation, Exactly? π€
Cross-compilation means compiling a binary on Machine A that runs on Machine B, where A and B have different architectures.
Your laptop is almost certainly running x86_64 β 64-bit Intel/AMD. Your Raspberry Pi is running ARM β a completely different instruction set. Normally, code compiled for x86_64 will not run on ARM. They speak different machine languages.
Cross-compilation lets you build an ARM binary on your x86_64 laptop. No SSH. No waiting for the Pi to compile. No "let me just install Rust on the Pi real quick" β which, on a Pi Zero, is a 45-minute coffee break.
The resulting binary: copy it over, it runs. No interpreter. No runtime. No node_modules. Just a file that does the thing.
Why PHP and Node Developers Haven't Thought About This π
Here's the thing about interpreted languages: cross-compilation isn't a concept you encounter because there's nothing to compile. You copy files, you run them with the right interpreter.
// "Deploying" in PHP land
rsync -avz ./app/ pi@raspberrypi:/var/www/app/
// Then SSH in and hope PHP 8.1 is installed and mod_rewrite is enabled
// And that the Pi has enough RAM to run Composer
// ...this is fine
# "Deploying" a Node app to a Pi
scp -r ./app pi@raspberrypi:~/app
ssh pi@raspberrypi "cd app && npm install"
# 17 minutes later...
# ERROR: node-gyp failed to build native addon
# (screaming internally)
With Rust, your binary is the runtime. There is no interpreter to install. There's no node_modules to transfer. You compile once on your fast laptop, ship a single file, done.
The RF/SDR Motivation π‘
For my RF/SDR projects, I needed this badly.
My setup: RTL-SDR dongle plugged into a Raspberry Pi 3B, sitting near a window with a vertical antenna. It continuously scans a frequency range, logs signal detections, and pipes data to my home server.
Writing the signal processing code in Python was fine for prototyping. But running it in production on a Pi meant:
- Slow startup times
- High CPU usage for continuous FFT computations
- Occasionally running out of memory mid-scan
- Installing Python + pip + every library on a fresh Pi from scratch
Rewriting it in Rust gave me faster processing and single-binary deployment. But I wasn't about to compile Rust on the Pi itself β rustc needs more RAM than the Pi 3 wants to spare, and a release build of anything serious takes forever on ARM.
Cross-compilation was the answer.
Setting It Up (The Surprisingly Easy Part) π§
Here's the thing that surprised me after all my PHP/Node deployment trauma: setting up Rust cross-compilation is actually... not that bad.
Step 1: Add the target
rustup target add armv7-unknown-linux-gnueabihf
# For Pi Zero / Pi 1: arm-unknown-linux-gnueabihf
# For Pi 4 (64-bit OS): aarch64-unknown-linux-gnu
That one command tells Rust's toolchain "hey, I want to be able to produce binaries for this ARM chip."
Step 2: Install the linker
Rust's compiler does the heavy lifting, but you still need a cross-linker β a tool that knows how to link ARM binaries:
# On Ubuntu/Debian (or WSL)
sudo apt install gcc-arm-linux-gnueabihf
# On macOS with Homebrew
brew tap messense/macos-cross-toolchains
brew install armv7-unknown-linux-gnueabihf
Step 3: Tell Cargo to use it
Create .cargo/config.toml in your project:
[target.armv7-unknown-linux-gnueabihf]
linker = "arm-linux-gnueabihf-gcc"
Step 4: Build
cargo build --release --target armv7-unknown-linux-gnueabihf
That's it. Your binary is at target/armv7-unknown-linux-gnueabihf/release/your-app.
Copy it to the Pi, make it executable, run it. No interpreter. No dependencies. Just a 3MB binary that does exactly what you built it to do.
The first time this worked for me I genuinely stared at the Pi's terminal for a few seconds waiting for an error that never came.
The Docker Approach (When Things Get Complicated) π³
If your project pulls in C libraries β like librtlsdr for talking to the SDR dongle, or libssl for TLS β things get more involved. You need to cross-compile those C dependencies too, not just your Rust code.
The cleanest solution: use cross, a Docker-based cross-compilation tool from the Rust community.
cargo install cross
cross build --release --target armv7-unknown-linux-gnueabihf
cross automatically pulls a Docker image with the right compiler toolchain, sysroot, and common C libraries pre-configured. You don't have to think about any of it. It just builds the ARM binary.
For my SDR project with rtlsdr-rs bindings, cross handled the native library linking with zero configuration on my part. Coming from fighting node-gyp for hours, this was surreal.
Shipping It π
Once you have the binary:
scp target/armv7-unknown-linux-gnueabihf/release/sdr-logger pi@raspberrypi:~/
ssh pi@raspberrypi "chmod +x ~/sdr-logger && ./sdr-logger"
Or, for a Proper Production Deploymentβ’, use rsync and a systemd service:
rsync -avz target/armv7-unknown-linux-gnueabihf/release/sdr-logger pi@raspberrypi:/usr/local/bin/
# /etc/systemd/system/sdr-logger.service on the Pi
[Service]
ExecStart=/usr/local/bin/sdr-logger
Restart=always
A systemd service for a Rust binary. No Python environment to activate. No Node version manager to run. No checking if the right gems are installed. The binary is the service. This is what "zero-dependency deployment" actually feels like.
The Performance Dividend β‘
Beyond deployment convenience, there's another reason to do this for Pi projects: performance.
The Raspberry Pi 3B's ARM Cortex-A53 is not a fast chip. It has 1GB of RAM. Running a Python script that does continuous FFT processing, file I/O, and network writes will peg one of those cores and occasionally stutter.
The same logic in a Rust binary, compiled with --release and opt-level = 3, runs with a fraction of the CPU usage. The Pi's other cores stay available. Memory usage is predictable. Startup time is under a second.
For my signal logger, the difference was a Python process using ~80% of a single core versus a Rust binary using ~15%. The Pi runs cooler. The battery backup lasts longer. The scan rate doubles.
That's the Rust systems programming pitch, applied to a $35 computer listening to radio signals from my roof.
What I'd Tell My Past Self π‘
Coming from the web dev world, I thought cross-compilation sounded terrifying β the kind of thing only embedded systems engineers with grey beards and oscilloscopes understand.
It's not. The Rust toolchain makes it genuinely approachable:
rustup target addhandles the Rust sidecrosshandles the C library side- The output is a single binary that runs on the Pi without installing anything
- Your deploy script becomes
scpandchmod +x
If you have any project β hobby, side project, work tool β that needs to run on a different architecture, cross-compilation is worth understanding. And Rust makes it more accessible than any other systems language I've encountered.
PHP runs everywhere because you install PHP everywhere. Rust runs everywhere because you compiled it for there. It's a fundamentally different relationship with your runtime, and it's one of the more satisfying transitions of my programming career so far.
TL;DR π―
- Cross-compilation means building an ARM binary on your x86_64 laptop that runs on your Raspberry Pi
- No runtime to install on the Pi β Rust binaries are self-contained
rustup target addadds the target architecture in one commandcross buildhandles projects with native C library dependencies via Docker- Single binary deploy β
scpthe file,chmod +x, run it; no interpreter, nonode_modules, no package manager - Real performance wins on constrained hardware like the Pi, where interpreted languages burn CPU on overhead your Rust binary doesn't have
The first time your Pi runs a binary you compiled on your laptop in under two seconds, with no SSH gymnastics or missing library errors, you'll understand why systems programmers have been talking about this for decades. π¦β‘
Running Rust on a Pi or curious about cross-compilation for embedded/ARM targets? Find me on LinkedIn β always happy to compare notes on making small computers do big things without melting them.
SDR and Rust experiments: GitHub β where antennas meet the borrow checker and somehow it all works.
Now go rustup target add armv7-unknown-linux-gnueabihf and ship something to a computer smaller than your phone. π₯§π¦β¨