Introduction

Since the Raspberry Pi can run a full Linux operating system that provides a graphical user interface - so you can open a desktop, create files and folders, install programs, browse the web, write code, compile and run it - your first instinct might be to develop, and compile your Rust program directly on the Pi, just like on a regular PC.

And yes, this is possible - but there are two significant problems with this approach:

  • ⚠️ Performance limitations - Most Raspberry Pi models (especially the Zero and Zero 2 W) are equipped with low-end hardware. Once you open a few windows or a browser tab, the system can become noticeably laggy. Typing feels delayed, UI becomes unresponsive, and more importantly compiling Rust code - especially large crates - can be painfully slow.

  • ⚠️ Toolchain installation issues - On some models, like the Raspberry Pi Zero 2 W (which has only 512 MB of RAM), you might not even be able to install the Rust toolchain (rustc, cargo) properly which is required to compile Rust code. The installation process itself is resource-heavy and may fail due to memory constraints. I ran into this exact issue: I was unable to install the Rust toolchain on a Zero 2 W due to insufficient RAM.

So what is the alternative?

Cross-compilation

A better solution is to write and compile your code on a more powerful development machine (like a modern PC), then simply send the ready-to-run binary (aka. executable) to your Raspberry Pi and execute it there. This approach is called cross-compilation.

Even better, you can send the executable and run it remotely via SSH - a network protocol that lets you securely connect to and control another computer over the network using the command line.

To do this, you will first need to find out the local network IP address assigned to your Raspberry Pi board. There are a couple of ways to do that:

  • If you have a screen connected to the Pi, open a terminal and run command: hostname -I. It will display the IP address you are looking for.
  • Alternatively, log into your Wi-Fi router’s admin page. In the list of connected devices, search for one named “raspberrypi” - its IP address should be listed there.

Workflow: compile, send, and run

The typical workflow for developing a Rust program for Raspberry Pi using cross-compilation looks like this:

  1. Write and compile your code on your development machine.
    ❗NOTE: Compilation requires some additional configuration, which is explained in the next section.

  2. Start Raspberry Pi and make sure it is connected to the same local network as your PC.

  3. Copy compiled binary from your PC to Raspberry Pi using scp command:
    scp <MY_PROGRAM_ON_DEV_MACHINE>/<BUILD_DIRECTORY>/<BINARY> <USER>@<RASPBERRY_PI_IP>:/home/<USER>/<DESTINATION_DIRECTORY>/

    For example:
    scp /home/mattszymonski/hello_world/target/aarch64-unknown-linux-gnu/release/hello_world mattszymonski@192.168.0.130:/home/mattszymonski/hello_world

  4. Login to your Raspberry Pi via SSH using ssh command:
    ssh <USER>@<RASPBERRY_PI_IP>

    For example:
    ssh mattszymonski@192.168.0.130

  5. Make copied binary executable using chmod +x command:
    chmod +x /home/<USER>/<DESTINATION_DIRECTORY>/<BINARY>

  6. Run your program issuing in the console by issuing:
    ./<BINARY>

    For example:
    ./home/mattszymonski/hello_world

Project setup

One crucial thing to understand is that Rust programs are compiled for a specific target system - this includes the CPU architecture and the operating system.

For example, a Rust program compiled on a Windows PC won’t run on a Linux-based Raspberry Pi, because the binary is built for a different architecture and OS.

To build a Rust program for a different platform (like the Raspberry Pi), you need to tell the Rust compiler which target platform you want to build for.
This is typically done by creating an additional configuration file: <MY_PROGRAM>/.cargo/config.toml.

Inside this file, you define the target architecture and optionally specify which linker to use like:

config.toml

1
2
[target.<TARGET_TRIPLE>]
linker = "<LINKER_NAME>"

This configuration block tells Rust:

  • target.<TARGET_TRIPLE> - Specifies the system you are building for (architecture, OS, and ABI).
  • linker = "<LINKER_NAME>" - Tells Rust which cross-compiler to use to produce binaries for that target. You need to have it installed on your development machine.

Once this is set up, you can build the program for your target with command:
cargo build --target <TARGET_TRIPLE>
Cargo will automatically use the linker assigned to this target in your config file.

This config file is optional. It is possible to pass everything directly in the build command: RUSTFLAGS="-C linker=<LINKER_NAME>" cargo build --target <TARGET_TRIPLE>

Example configuration triples and their linkers

64-bit Raspberry Pi OS

Models: Pi 3, 4, 5, Zero 2 W

config.toml

1
2
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"

To install this linker on Debian/Ubuntu Linux machine issue:
sudo apt install gcc-aarch64-linux-gnu

32-bit Raspberry Pi OS

Models: Pi 2, 3, Zero 2 W

config.toml

1
2
[target.armv7-unknown-linux-gnueabihf]
linker = "arm-linux-gnueabihf-gcc"

To install this linker on Debian/Ubuntu Linux machine issue:
sudo apt install gcc-arm-linux-gnueabihf

Older Raspberry Pi models

Models: Pi 1, Pi Zero original

config.toml

1
2
[target.arm-unknown-linux-gnueabihf]
linker = "arm-linux-gnueabihf-gcc"

Same linker as above

Full process example

So how do you do it step by step?
Below is a minimal example of a Rust program that blinks an LED connected to the Raspberry Pi’s GPIO pins I wrote and compiled on my Debian Linux machine for Raspberry Pi Zero 2 W:

  1. Install Rust toolchain on development machine to be able to compile Rust code.

  2. Create new Rust project using command:
    cargo new /home/mattszymonski/coding/blinking-diode

  3. Change folder with:
    cd /home/mattszymonski/coding/blinking-diode

  4. Setup project files as follows:

/blinking-diode/.cargo/config.toml

1
2
3
// ! Adjust to meet your target Raspberry Pi requirements
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc" 

/blinking-diode/Cargo.toml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
[package]
name = "blinking-diode"
version = "0.1.0"
edition = "2021"

[[bin]]
name = "blinking-diode"
path = "main.rs"

[dependencies]
rppal = "0.19.0" 

/blinking-diode/main.rs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use std::error::Error;
use std::thread;
use std::time::Duration;

use rppal::gpio::Gpio;
use rppal::system::DeviceInfo;

// GPIO uses BCM pin numbering. BCM GPIO 23 is tied to physical pin 16.
const GPIO_LED: u8 = 23;

fn main() -> Result<(), Box<dyn Error>> {
    println!("Blinking an LED on a {}.", DeviceInfo::new()?.model());

    let mut pin = Gpio::new()?.get(GPIO_LED)?.into_output();

    // Blink the LED by setting the pin's logic level high for 500 ms.
    pin.set_high();
    thread::sleep(Duration::from_millis(500));
    pin.set_low();
    thread::sleep(Duration::from_millis(500));
    pin.set_high();
    thread::sleep(Duration::from_millis(500));
    pin.set_low();
    thread::sleep(Duration::from_millis(500));
    pin.set_high();
    thread::sleep(Duration::from_millis(500));
    pin.set_low();

    Ok(())
}
  1. Install required linker using command:
    sudo apt install gcc-aarch64-linux-gnu

  2. Compile program using command:
    cargo build --release --target aarch64-unknown-linux-gnu

    The result will be outputed to:
    /home/mattszymonski/coding/blinking-diode/target/aarch64-unknown-linux-gnu/release/blinking-diode

  3. Copy compiled binary from development machine to Raspberry Pi by executing:
    scp /home/mattszymonski/coding/blinking-diode/target/aarch64-unknown-linux-gnu/release/blinking-diode mattszymonski@192.168.0.130:/home/mattszymonski/programs/blinking-diode

  4. Make copied binary executable using command:
    chmod +x /home/mattszymonski/programs/blinking-diode

  5. Run it and enjoy!:
    ./home/mateusz/programs/blinking-diode