I had a small itch: a CLI that walks a directory, reads a pile of YAML config files, validates them against some rules I care about, and tells me which ones are wrong before I deploy them. The sensible answer is a shell script with yq and a loop. I have written that script a dozen times. This time I wrote it in Rust, partly to learn and partly out of stubbornness, and I have been arguing with myself about whether it was worth it ever since.
the good
The crate ecosystem for this kind of thing is lovely now. structopt gives you argument parsing from a struct definition, derive macros and all, so the entire CLI surface is declared in about fifteen lines and you get --help for free. serde with serde_yaml deserialises straight into typed structs, which means a malformed config is a parse error with a line number, not a silent empty string three steps later.
#[derive(StructOpt)]
struct Opts {
/// Directory to scan
#[structopt(parse(from_os_str))]
path: PathBuf,
/// Treat warnings as errors
#[structopt(long)]
strict: bool,
}
And the result is a single static binary. No runtime to install on the target box, no "which version of yq is this", no PATH archaeology. I scp one file and it runs. For a tool I want to drop onto servers, that alone is most of the argument.
the cost
It took an evening, not twenty minutes. The borrow checker and I had the usual conversation about who owns a string, and I lost twice before I understood the question. Error handling in Rust is correct and explicit, which is wonderful when it compiles and tedious when you just want to print a message and move on. I reached for ? and a custom error type and felt slightly silly doing all that for a config linter.
The compile times are not fast, either. A clean release build of a tiny tool still takes longer than I would like, and the dependency tree pulls in more than you would guess from the source.
was it worth it
For this tool, on balance, yes, but not for the reasons I expected. The static binary is the real prize and it would have justified the effort on its own. The type safety caught two config-shape bugs at compile time that the bash version would have shipped. What it did not do is save me time today, and if I needed it finished by lunch I would have written the script.
The thing I keep coming back to is that the Rust version is one I will still trust in a year, and the shell version is one I would quietly rewrite the moment it grew a third feature. That is not nothing. It is, in fact, most of why I keep reaching for Rust on the small tools as well as the large ones, even when the maths for any single afternoon does not obviously add up.