Why Rust for SDR?

A Case for Migrating from C/C++

R4W Development Team
(Aida, Joe Mooney, Claude Code)

December 2025

Why Rust for SDR?

Making the Case to C/C++ Developers

The Elephant in the Room

“We’ve been doing SDR in C/C++ for decades. Why change?”

This presentation addresses: 1. The problems we’re actually solving 2. What Rust brings that C/C++ doesn’t 3. Performance comparisons (real numbers) 4. Migration strategies 5. The things Rust is NOT good at

What We’re Solving

Memory Safety Bugs in SDR Codebases

Bug Class Example Consequence
Buffer overflow Wrong FFT size Crash, data corruption
Use-after-free Callback with freed context Random behavior
Data race TX/RX thread conflict Intermittent failures
Null pointer Missing device check Segfault

Real impact: Hours/days of debugging, sanitizer runs, code review.

The C/C++ Response

“We use AddressSanitizer”

  • Catches bugs at runtime
  • Slows execution 2x
  • Doesn’t catch everything
  • You still have to hit the bug

“We have code review”

  • Humans miss things
  • Review fatigue is real
  • Not all reviewers understand all code

“We’ve been careful for years”

  • Staff turnover
  • Deadline pressure
  • “It worked before”

What Rust Actually Does

Compile-Time Prevention

// This doesn't compile:
let data = vec![1, 2, 3];
let reference = &data[0];
data.push(4);  // ERROR: cannot borrow `data` as mutable
               // because it is also borrowed as immutable
println!("{}", reference);

The compiler catches: - Buffer overflows - Use-after-free - Data races - Null pointer dereference

Real DSP Code Comparison

Rust

pub fn apply_fir_filter(samples: &mut [IQSample], coeffs: &[f64]) {
    for i in coeffs.len()..samples.len() {
        samples[i] = coeffs.iter()
            .enumerate()
            .map(|(j, &c)| samples[i - j] * c)
            .sum();
    }
}  // Bounds checked at compile time

C

void apply_fir_filter(complex_t* samples, size_t n,
                      double* coeffs, size_t m) {
    for (size_t i = m; i < n; i++) {
        samples[i] = 0;
        for (size_t j = 0; j < m; j++) {
            samples[i] += samples[i - j] * coeffs[j];  // Hope indices are valid!
        }
    }
}

“But Performance?”

Common Misconception

“Memory safety checks must cost performance.”

Reality

Rust’s safety is compile-time, not runtime.

  • No bounds checking in release builds (when optimizable)
  • Same LLVM backend as Clang
  • No garbage collector
  • Zero-cost abstractions

Benchmark: R4W vs GNU Radio

Operation R4W (Rust) GNU Radio (C++) Speedup
FFT 1024-pt 371 MS/s 50 MS/s 7.4x
FFT 4096-pt 330 MS/s 12 MS/s 27x
FFT 2048-pt 179 MS/s ~25 MS/s 7x

Why faster? - rustfft optimized for Rust memory model - Zero-copy lock-free buffers - No Python/SWIG overhead

Real-Time Validation

Metric Target R4W Actual
FFT p99 latency < 100 µs 18 µs
BPSK roundtrip p99 < 100 µs 20 µs
FHSS hop timing p99 < 500 µs 80-118 µs
Page faults (RT mode) 0 0
Hot-path allocations 0 0

Same RT primitives as C: mlockall, SCHED_FIFO, CPU affinity.

“I Can’t Abandon My C Libraries”

FFI is Zero-Cost

// Calling C from Rust
extern "C" {
    fn fftw_plan_dft_1d(n: c_int, in_: *mut c_double,
                        out: *mut c_double, sign: c_int,
                        flags: c_uint) -> *mut fftw_plan;
}

Exposing Rust to C

// r4w.h (generated by cbindgen)
typedef struct r4w_waveform_t r4w_waveform_t;
r4w_waveform_t* r4w_waveform_bpsk(double sample_rate, double symbol_rate);
size_t r4w_modulate(r4w_waveform_t* wf, const bool* bits, size_t len, float* out);

Migration Strategies

Phase 1: Use Rust as a Library

Keep existing C/C++ application, call R4W for specific functions.

#include <r4w.hpp>
auto lora = r4w::Waveform::lora(7, 125000.0);
auto samples = lora.modulate(bits);

Phase 2: Port Pure Functions

Start with stateless DSP functions: - Filters - FFT wrappers - Modulation algorithms

Phase 3: New Development in Rust

Legacy maintenance in C++, new features in Rust.

What Rust is NOT Good At

Learning Curve

  • Borrow checker takes time to internalize
  • Different mental model than C++
  • 2-4 weeks to productivity

Ecosystem Gaps

  • Fewer SDR libraries than C++
  • Some hardware SDKs are C-only
  • Smaller community (growing)

When NOT to use Rust

  • Tiny microcontrollers (< 32KB RAM)
  • Quick prototypes
  • When team refuses to learn

The R4W Value Proposition

What You Get

Aspect Value
Memory safety Bugs caught at compile time
Performance Equal or better than C++
Tooling cargo, rust-analyzer, clippy
Testing 527 tests across 8 crates
Docs Auto-generated from code
FPGA Zynq + Lattice acceleration
Isolation 8 levels of waveform sandboxing

What You Give Up

  • Familiarity (temporarily)
  • Some C++ libraries (need wrappers)

R4W Code Metrics

Language Lines Files Purpose
Rust 66,572 217 Core implementation (79%)
Coq 6,324 27 Formal verification
C/C++ 2,037 5 FFI bindings
Total 84,467 359
  • 38+ waveforms implemented
  • 6,324 lines of formal proofs
  • 1 test per 126 lines of Rust

Try It Yourself

# Clone and build
git clone https://github.com/joemooney/r4w
cd r4w && cargo build --release

# Run benchmarks
cargo bench

# Run 527 tests
cargo test

# Explore the code
cargo doc --open

Resources for C/C++ Developers

Books

  • “The Rust Programming Language” (free online)
  • “Rust for Rustaceans” (intermediate)

Tutorials

  • Rustlings exercises
  • Rust by Example

SDR-Specific

  • R4W Workshop Materials
  • crates/r4w-ffi/examples/

Questions?

Common Follow-ups

  • “What about SIMD?” → Rust has std::simd and external crates
  • “Embedded targets?” → Rust supports ARM, RISC-V, etc.
  • “Build systems?” → Cargo + CMake integration available
  • “Debugging?” → GDB/LLDB work fine, rust-gdb wrapper available

Summary

If you value… Rust provides…
Correctness Compile-time safety
Performance Zero-cost abstractions
Maintainability Strong typing, modern tooling
Velocity Fast builds, integrated testing

The question isn’t “Why Rust?”

It’s “Why are we still debugging buffer overflows?”