The .frm File on a Shelf
In 1999, I opened Visual Basic 6 and built a desktop calculator called Project for Electronics. It solved Ohm's Law and Watts Law — enter any two of voltage, current, resistance, or power, and the program computed the other two. It worked. It was useful. Then it sat on a floppy disk for a quarter century.
I recently found the source — a single Main.frm file, 13 KB, packed with every classic VB6 antipattern you'd expect from that era. Long integers for electrical values. A For loop brute-forcing square roots. A 1 ms Timer control polling text boxes because VB6 had no event-driven input binding. GoTo labels scattered like confetti. It was beautiful in the way that a 1970s muscle car is beautiful: wrong in every measurable sense, but you have to respect the intent.
This is the story of porting that program to Rust, fixing every bug, expanding it from one calculator to six, adding a headless daemon mode, and then putting the whole thing under AGPL-3.0 — partly for principle, mostly for the joke.
What Was Wrong (Everything)
Before writing a single line of Rust, I catalogued every defect in the VB6 source. Some were bugs; others were architectural decisions that made sense in 1999 but are radioactive today.
Long (16-bit int) for watts, volts, amps, ohms — overflows above 32,767
Brute-force For loop to approximate square roots
1 ms Timer control polling all text boxes
No input validation (negative resistance accepted silently)
GoTo Calculate, GoTo ClearFields for flow control
Format(Value, "0.00") — fixed 2 decimal places always
Ohm symbol displayed as "Ohms" (no Unicode)
f64 throughout — 64-bit double precision, no overflow
(x).max(0.0).sqrt() — one call, hardware-accurate
iced's reactive update() — recalculate only on change
Negative power/resistance rejected with error output
Structured match with priority-ordered pair matching
5-decimal precision with trailing-zero trimming via trim_f()
Proper Unicode Ω everywhere
The Six Calculators
What started as a single Ohm's Law tool grew into a suite I'm calling MCP-Relational-Data that bridges two worlds: electronics engineering and modern business/AI economics. The GUI presents all six calculators in a two-column layout.
| Calculator | Domain | Inputs |
|---|---|---|
| Ohm's Law & Watts Law | Electronics | Any 2 of P, I, R, V |
| Margin & Markup | Ecommerce | Cost, Selling Price |
| ROI | Business | Investment, Revenue |
| AI Token Cost | LLM Economics | Model, Input/Output Tokens |
| Electricity Cost | Infrastructure | Watts, Hours, Days, Rate |
| Break-Even | Business | Fixed Costs, Price, Variable Cost |
The Ohm's Law solver uses priority-ordered pair matching — it tries all six valid input combinations (I+R, I+V, I+P, R+V, V+P, P+R) in a specific order and computes all four values from whichever two are non-zero. The AI Token Cost estimator cycles through six models (GPT-4o, GPT-4, GPT-3.5 Turbo, Claude 3.5 Sonnet, Claude 3 Opus, Claude 3 Haiku) with per-million-token pricing built into the source.
Architecture: calc.rs Is the Soul
The most important design decision was separating pure calculation logic from every presentation layer. calc.rs contains zero I/O, zero GUI imports, zero serde. It's just functions: numbers in, numbers out. Both the iced GUI and the JSON daemon call these same functions.
// calc.rs — the entire Ohm's Law solver
pub fn ohm_calculate(p: f64, i: f64, r: f64, v: f64) -> (f64, f64, f64, f64) {
if i != 0.0 && r != 0.0 { return (i * i * r, i, r, i * r); }
if i != 0.0 && v != 0.0 { return (i * v, i, v / i, v); }
if i != 0.0 && p != 0.0 { let v = p / i; return (p, i, v / i, v); }
if r != 0.0 && v != 0.0 { let i = v / r; return (i * v, i, r, v); }
if v != 0.0 && p != 0.0 { let i = p / v; return (p, i, v * v / p, v); }
if p != 0.0 && r != 0.0 {
let i = (p / r).max(0.0).sqrt();
let v = (p * r).max(0.0).sqrt();
return (p, i, r, v);
}
(p, i, r, v)
}
The file structure mirrors this separation:
src/
main.rs — CLI flag parsing, feature-gated dispatch
calc.rs — Pure functions (no dependencies)
gui.rs — iced 0.12 desktop app (behind #[cfg(feature = "gui")])
daemon.rs — stdin/stdout JSON API (behind #[cfg(feature = "daemon")])
The Daemon: JSON Over Stdout
The original VB6 program could only be used by a human sitting at a Windows PC. The daemon mode changes that entirely. It reads one JSON object per line from stdin and writes one JSON object per line to stdout. Status messages go to stderr so they never pollute the pipe.
$ echo '{"tool":"ohm","current":2,"resistance":4}' | mcp-relational-data --daemon
{"tool":"ohm","power":"16 Watts(W)","current":"2 Amps(A)",
"resistance":"4 Ohms(\u03A9)","voltage":"8 Volts(V)"}
This makes it trivial to call from Python, Node.js, shell scripts, or any MCP-compatible server wrapper. You can even build a slim daemon-only binary that skips the iced GUI dependency entirely:
cargo build --release --no-default-features --features daemon
The protocol supports three operations: list (enumerate tools), help (get parameter docs for a tool), and any calculator name with its required parameters. Invalid JSON or unknown tools return structured error objects — no panics, no crashes.
The iced 0.12 Adventure
Using iced 0.12 was its own challenge. The version available had some API gaps compared to the documentation — no Container::border(), no container::Style or container::Status types, no rule::horizontal(). Every border, separator, and styled container I tried to add failed to compile.
The solution was brutal minimalism: stick to only the APIs confirmed to work (container, column, row, text, text_input, button, scrollable) and use spacing and padding to create visual separation. It ended up looking cleaner than bordered panels would have anyway.
There was also a subtle lifetime error: using &String::new() as a static reference for the break-even calculator's empty output field caused a dangling reference at compile time. The fix was trivial — use "" (a &'static str) instead — but tracking it down took a few rounds of compiler error archaeology.
Why AGPL?
"If you host this as a network service and modify it, you owe the world the source. Consider it a 25-year-later laugh at the VB6 original that was locked in a
.frmfile on a floppy disk."
The original program was proprietary by default — not because I made a deliberate licensing choice, but because that's what VB6 projects were. One file, one IDE, one OS. You couldn't build on it, extend it, or integrate it into anything else.
AGPL-3.0 is the opposite extreme. If you fork this calculator, wrap it in a web service, and charge people to use it, you must publish your modified source. It's a deliberately ironic choice for a program whose original incarnation was the least open thing possible: a compiled binary I distributed on a physical disk in 1999.
Practically, it doesn't matter. This is a calculator. Nobody is going to build a SaaS around Ohm's Law. But the license file is 662 lines long and it's there, and that's the point.
What's in the Box
The final deliverable is a clean Rust project with everything you need to build, run, and extend it:
Cargo.toml— feature-gated dependencies (gui + daemon default, either can be disabled)src/main.rs— CLI entry point with--daemon,--help,--versionsrc/calc.rs— six pure calculation engines + AI model pricing datasrc/gui.rs— iced 0.12 desktop app, two-column layout, reactive updatessrc/daemon.rs— stdin/stdout JSON protocol, MCP-friendlyREADME.md— full documentation with protocol reference, pair-matching table, VB6 bug listQUICKSTART.md— zero-to-running in 7 steps with Python, shell, and Node.js examplesLICENSE— the full GNU AGPL-3.0 text, all 662 lines of it
Try It
git clone https://git.dcos.net/dcosnet/MCP-Relational-Data.git
cd MCP-Relational-Data
cargo run # GUI mode
echo '{"tool":"ohm","voltage":12,"resistance":4}' | cargo run -- --daemon
A 1999 VB6 calculator I wrote as a teenager, now a modern Rust application with a desktop GUI, a headless JSON daemon, six calculators, and the most aggressively copyleft license I could find. The floppy disk would be proud.