Feb 24, 2026 · 10 min read
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.

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.
cargois 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:
- Install Rust:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - Create a project:
cargo new my-rf-tool && cd my-rf-tool - Add a crate:
cargo add rfconversions - 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:
| Python | Rust | |
|---|---|---|
| Distribution | pip install + environment hell | Single binary |
| Performance | Adequate for small chains | Fast for Monte Carlo, large datasets |
| Correctness | Runtime errors | Compile-time errors |
| Testing | pytest (manual setup) | cargo test (built in) |
| Airgapped use | Package everything | Copy one file |
For one-off scripts, use Python. For tools you'll maintain and share, consider Rust.