Blog
Feb 24, 2026 - 10 MIN READ
Rust for RF Engineering

Rust for RF Engineering

Why Rust is a great fit for RF engineering tools, and a tour of open-source crates for conversions, touchstone files, gain lineups, and link budgets.

Ian Cleary

Ian Cleary

Most RF engineers reach for MATLAB, Python, or spreadsheets. These work, but they share a problem: no type safety, no compile-time guarantees, and painful distribution. You end up emailing .xlsx files or maintaining a Python environment that breaks every 6 months.

Rust fixes this:

  • Units and types catch errors at compile time. Mixing dBm and dBW silently is a rite of passage in RF. Rust's type system can prevent it.
  • Single binary distribution. Ship a CLI or desktop app with no runtime dependencies. This matters enormously for defense and airgapped environments.
  • Performance without compromise. Monte Carlo link budgets, large Touchstone file parsing, and cascade analyses run fast without dropping to C.
  • cargo is a great package manager. Dependency management, testing, docs, and publishing are built in.

Open-Source RF Crates

These crates are available on crates.io and cover the core calculations RF engineers do daily.

rfconversions

Unit conversions that every RF engineer needs: dB ↔ linear, dBm ↔ watts, dBW ↔ watts, frequency ↔ wavelength, noise figure ↔ noise temperature, and P1dB compression.

use rfconversions::power;
use rfconversions::frequency;
use rfconversions::noise;

// Power conversions
let dbm = power::watts_to_dbm(1.0);        // 30.0 dBm
let watts = power::dbm_to_watts(30.0);      // 1.0 W
let dbw = power::watts_to_dbw(1.0);         // 0.0 dBW
let linear = power::db_to_linear(30.0);     // 1000.0

// Frequency ↔ Wavelength
let wavelength = frequency::frequency_to_wavelength(1.0e9); // 0.2998 m
let ghz = frequency::mhz_to_ghz(2400.0);   // 2.4 GHz

// Noise figure ↔ Noise temperature
let nf_db = noise::noise_figure_from_noise_factor(2.0);        // ~3.01 dB
let temp = noise::noise_temperature_from_noise_factor(2.0);     // 290.0 K

Small, zero-dependency, and well-tested. Use it as a foundation for larger tools.

touchstone

Parse, analyze, and manipulate Touchstone (.s1p, .s2p, .snp) files — the industry-standard format for S-parameter data from vector network analyzers. Supports any port count up to 32+.

use touchstone::Network;

// Load a 2-port network
let ntwk = Network::new("filter.s2p".to_string());

println!("Ports: {}", ntwk.rank);
println!("Format: {}", ntwk.format);
println!("Reference impedance: {} Ω", ntwk.z0);

// S21 (forward transmission) in dB
let s21_db = ntwk.s_db(2, 1);
for point in &s21_db {
    println!("f={} : S21={} dB", point.frequency, point.s_db.decibel());
}

// S11 (return loss) in real-imaginary
let s11_ri = ntwk.s_ri(1, 1);
for point in &s11_ri {
    println!("f={} : re={}, im={}", point.frequency, point.s_ri.real(), point.s_ri.imaginary());
}

// Cascade two 2-port networks
let net1 = Network::new("amp.s2p".to_string());
let net2 = Network::new("filter.s2p".to_string());
let cascaded = net1.cascade(&net2);

Supports Touchstone 1.0 and 2.0 formats, all parameter types (S, Y, Z, H, G), and all number formats (DB, MA, RI). Includes a CLI for plotting S-parameters as interactive HTML charts.

gainlineup

Model an RF signal chain as a sequence of blocks and cascade their effects on signal power, noise, and linearity. This is the spreadsheet-style RF lineup — but in Rust, with proper Friis equation cascading.

use gainlineup::{Block, Input, cascade_vector_return_vector};

// Define input signal
let input = Input {
    power_dbm: -80.0,
    frequency_hz: 6.0e9,        // 6 GHz C-band
    bandwidth_hz: 1.0e6,        // 1 MHz channel
    noise_temperature_k: Some(50.0),
};

// Define blocks in the chain
let lna = Block {
    name: "Low Noise Amplifier".to_string(),
    gain_db: 20.0,
    noise_figure_db: 1.5,
    output_p1db_dbm: Some(5.0),
    output_ip3_dbm: Some(20.0),
};

let mixer = Block {
    name: "Mixer".to_string(),
    gain_db: -8.0,
    noise_figure_db: 8.0,
    output_p1db_dbm: Some(10.0),
    output_ip3_dbm: Some(15.0),
};

let if_amp = Block {
    name: "IF Amplifier".to_string(),
    gain_db: 25.0,
    noise_figure_db: 4.0,
    output_p1db_dbm: Some(15.0),
    output_ip3_dbm: Some(25.0),
};

// Run the cascade
let blocks = vec![lna, mixer, if_amp];
let nodes = cascade_vector_return_vector(&input, &blocks);

// Each node has cumulative gain, noise figure, signal power
let output = &nodes[nodes.len() - 1];
println!("System Gain: {:.1} dB", output.cumulative_gain_db);
println!("System NF: {:.2} dB", output.cumulative_noise_figure_db);
println!("Output Power: {:.1} dBm", output.signal_power_dbm);

linkbudget

End-to-end satellite link budget calculations: EIRP, path loss, atmospheric losses, G/T, C/N, margin, modulation, BER, and FEC coding.

use linkbudget::{LinkBudget, PathLoss, Transmitter, Receiver, Modulation};
use linkbudget::coding;

let budget = LinkBudget {
    name: "Ka-band LEO downlink",
    bandwidth: 36e6,              // 36 MHz channel
    transmitter: Transmitter {
        output_power: 10.0,       // dBm
        gain: 35.0,               // dBi
        bandwidth: 36e6,
    },
    receiver: Receiver {
        gain: 40.0,               // dBi
        temperature: 290.0,       // K
        noise_figure: 2.0,        // dB
        bandwidth: 36e6,
    },
    path_loss: PathLoss {
        frequency: 20.0e9,        // 20 GHz Ka-band
        distance: 550.0e3,        // 550 km LEO
    },
    frequency_dependent_loss: Some(3.0), // rain fade margin
};

// RF metrics
println!("EIRP: {:.1} dBm", budget.transmitter.eirp_dbm());
println!("G/T: {:.1} dB/K", budget.receiver.g_over_t_db());
println!("Path Loss: {:.1} dB", budget.path_loss());
println!("C/No: {:.1} dB·Hz", budget.c_over_no());
println!("SNR: {:.1} dB", budget.snr());

// BER with QPSK
println!("Eb/No (QPSK): {:.1} dB", budget.eb_no_db(&Modulation::Qpsk));
println!("BER (uncoded): {:.2e}", budget.ber(&Modulation::Qpsk));
println!("Shannon capacity: {:.1} Mbps", budget.phy_rate().mbps());

// With FEC — DVB-S2 QPSK rate 3/4 (LDPC)
let coded = coding::dvbs2_qpsk_r34();
println!("Coded BER: {:.2e}", budget.ber_coded(&coded));
println!("Throughput: {:.0} Mbps", budget.throughput_bps(&coded) / 1e6);

Includes modules for Doppler shift, orbital mechanics, power flux density, quantization noise, EVM, and receiver sensitivity.

Getting Started

If you're an RF engineer who's never used Rust:

  1. Install Rust: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
  2. Create a project: cargo new my-rf-tool && cd my-rf-tool
  3. Add a crate: cargo add rfconversions
  4. Write and test: cargo test

The learning curve is real — the borrow checker will fight you for a week. But once it clicks, you'll wonder why you ever trusted a spreadsheet with your link budget.

Why Not Python?

Python is fine for quick analysis. But if you're building tools that others will use, Rust gives you:

PythonRust
Distributionpip install + environment hellSingle binary
PerformanceAdequate for small chainsFast for Monte Carlo, large datasets
CorrectnessRuntime errorsCompile-time errors
Testingpytest (manual setup)cargo test (built in)
Airgapped usePackage everythingCopy one file

For one-off scripts, use Python. For tools you'll maintain and share, consider Rust.

Ian Cleary • © 2026