I had a shell script that had outgrown being a shell script. It started as ten lines to tidy up some files, and over a couple of years it had grown argument parsing, a config file, error handling that was mostly || echo "failed", and a stretch of sed that I could no longer read without running it to remind myself what it did. Every time I touched it I broke something quoting-related. It was time.
The question wasn't really "could I write this in Rust", it was "should I spend an evening doing it". So I kept rough track of where the time went, because I was curious whether the answer would justify itself.
Where the time actually went
The core logic took about an hour. clap gave me argument parsing with --help and sensible errors for free, which immediately replaced the most fragile part of the script.
#[derive(Parser)]
#[command(version, about)]
struct Cli {
/// Directory to process
#[arg(short, long, default_value = ".")]
path: PathBuf,
/// Show what would happen without doing it
#[arg(long)]
dry_run: bool,
}
The rest of the evening went on the things the shell script had been quietly getting wrong. Paths with spaces. A file that disappeared between the listing and the processing. What happens when the config file is malformed rather than absent. The script had "handled" all of these by ignoring them and occasionally doing something daft. Rust made me say what should happen in each case, and that was most of the work, and most of the value.
The borrow checker and I had two disagreements, both my fault, both resolved by stopping and thinking about ownership for a minute rather than fighting it. That's a much better relationship than I had with it a couple of years ago, and I suspect that's me improving more than the compiler softening.
So, worth it?
For this script, yes, but not for the reasons I expected going in.
It wasn't speed. The script was fast enough and the Rust version isn't doing anything that benefits from being faster. A user wouldn't notice the difference and neither do I.
It wasn't really the single binary either, though cargo build --release producing one file I can drop on any of my machines is genuinely pleasant, and not having to think about whether the target box has the right version of bash or the right sed is a small daily relief.
The real win was that the rewrite forced every fuzzy edge into the open. The shell script worked right up until it didn't, in ways I couldn't predict. The Rust version makes its failure modes explicit, so when it does break I'll get an error that tells me what happened rather than silence and a corrupted directory.
The catch, and it's a real one: this only pays off because I'll keep using the tool for years. If it were a throwaway, the shell script was the correct answer and Rust would have been an indulgence dressed up as engineering. The thing I'm getting better at isn't writing Rust, it's being honest about which of my scripts have earned the upgrade. This one had. Most haven't.