I had a shell script. It tailed a few log files, matched some patterns, and printed a coloured summary. It was forty lines of bash and it worked, right up until it didn't, which was the morning someone fed it a path with a space in it.
So I rewrote it in Rust. This is the part where I tell you whether that was a sensible use of an afternoon.
The honest answer: mostly yes, with caveats.
what got better
The thing I'd underestimated was how much of my bash was defensive plumbing. Quoting. Checking a file existed. Catching the case where grep returned nothing and the script happily carried on as if all was well. In Rust most of that just stops being my problem. std::fs::File::open hands me a Result, and the compiler will not let me forget it exists.
let f = File::open(&path)
.map_err(|e| format!("can't open {}: {}", path.display(), e))?;
That ? does the work my bash error handling never quite did. A path with a space in it is just a PathBuf now, and nobody cares.
For argument parsing I reached for clap, which at version 2 is genuinely pleasant. You describe your flags, you get --help for free, and bad input gets a sensible error instead of an array index panic. The whole CLI surface is declarative and it reads well six weeks later, which is the real test.
Performance was a bonus I didn't ask for. The bash version forked grep, sed and awk per file; the Rust version reads each file once and does the matching inline. On a few hundred megabytes of logs it went from roughly two seconds to under one hundred milliseconds. I did not need that speed. It was nice anyway.
what got worse
Compile times. Obviously. A fresh cargo build --release on this tiny thing is still tens of seconds, and the first build after pulling in clap and regex made me go and put the kettle on. For a one-line change to a script, bash wins on iteration speed and it isn't close.
The binary is also a few megabytes where the script was a few hundred bytes. I know, I know, statically linked, runs anywhere, no runtime to install. All true. Still felt odd shipping that for something so small.
And the borrow checker did make me stop and think twice about how I was passing around the matched lines. Nothing I'd call a fight, this isn't async or threading, but enough to remind me Rust asks for your attention up front in exchange for not asking for it later.
was it worth it
For a script I run by hand once a week, probably not. For a tool three other people now run on production boxes, where a silent failure means we miss an alert, absolutely. The value wasn't speed or even correctness on the happy path. It was that the failure modes are now visible. The compiler made me handle the cases I'd been ignoring, and that's exactly the property I want in something other people depend on.
I'm not going to rewrite everything in Rust. But the next small tool that needs to be reliable rather than just quick? That's going straight into cargo new.