The include_str!() Pattern β Embedding Python ML in a Rust Binary
Nightingale's pragmatic compromise β single binary distribution, persistent process communication, zero version mismatch
A pattern discovered in Nightingale (Rust karaoke app). A practical architecture for handling Python ML stacks from Rust.
The Problem: Running Python ML from Rust
Nightingale needs UVR/Demucs (vocal separation) and WhisperX (speech recognition). Both are PyTorch-based and only work properly in Python.
Typical options and their problems:
Depend on system Python β version conflicts, package collisions, "works on my machine" syndrome.
Ship .py files separately β file loss, path issues, users accidentally deleting scripts.
PyO3 RustβPython bindings β fine for simple functions, but binding heavy ML stacks (PyTorch + WhisperX + audio-separator) is dependency hell.
Rewrite everything in Rust β impractical. ML ecosystem (PyTorch, Hugging Face, ONNX Runtime) is Python-centric.
Nightingale's Solution: include_str!() + Persistent Process
const STEMS_PY: &str = include_str!("../analyzer/stems.py");
const SERVER_PY: &str = include_str!("../analyzer/server.py");
// ... 10 Python files
Rust's include_str!() macro includes file contents as string constants at compile time. Not runtime file reading.
How It Works
Build time: cargo build embeds 10 .py files as strings β single binary output.
First launch (bootstrap): Create ~/.nightingale/vendor/ β extract .py strings to files β install Python 3.10 via uv β create venv β install PyTorch, WhisperX β write .ready marker.
Analysis: Rust spawns server.py as child process β Python loads WhisperX model β waits β Rust sends JSON via stdin β Python responds via stdout β server stays alive for next song.
5 Benefits
1. Single binary distribution β one file to ship.
2. Zero version mismatch β Rust and Python code from same commit.
3. Tamper-proof β users can't accidentally modify embedded scripts.
4. Persistent Python server β ML models stay loaded between songs. CUDA OOM? Kill and respawn without crashing the app.
5. Simple JSON communication β no FFI complexity, easy debugging.
When This Pattern Fits
Python-dominant domains (ML inference)
Low call frequency (per-song, not per-millisecond)
Negligible IPC overhead
Desktop apps where deployment simplicity matters
When It Doesn't Fit
Thousands of calls per second β use PyO3 or ONNX Runtime Rust bindings
Hundreds of Python files β binary bloat
Bidirectional streaming β use gRPC or sockets
Core Insight
"Let Python do what Python does best (ML), but let Rust control deployment and orchestration." Instead of cramming both languages into one process via FFI, separate processes but bundle deployment. Low coupling, low deployment complexity.
Key Concepts
Embed 10 Python scripts into Rust binary at compile time via include_str!()
Extract as .py files to ~/.nightingale/vendor/analyzer/ on first run
Auto-install Python 3.10 + PyTorch + ML packages via uv (bootstrap)
Spawn server.py as persistent child process β WhisperX model stays loaded
Communicate analysis requests, progress, completion via stdin/stdout JSON protocol
On CUDA OOM: kill server β clean GPU β respawn (app stays alive)