{
"path": "/rust-cli-tips.html",
"site": "at://did:plc:x67qh7v3fd7znbdhauc45ng3/site.standard.publication/3mjcd2t6afe25",
"$type": "site.standard.document",
"title": "5 Tips for Writing Small CLI Tools in Rust",
"updatedAt": "2017-08-31T00:00:00.000Z",
"publishedAt": "2017-08-31T00:00:00.000Z",
"textContent": "[Rust] is a great language to write small command line tools in. While it gives you some tools for common tasks, allows nice abstractions, it also has a type system and approach to API design that lead you to write robust code. Let me show you some techniques to make this a nice experience.\n\nUpdate (Jan 2018): I published a crate (Rust library) that contains a lot of what this post describes: [quicli].\n\nUpdate (April 2020): Changed failure for anyhow.\n\n[quicli]: https://github.com/killercup/quicli\n\nContents\n\n1. Table of contents\n\nQuick CLI argument handling\n\nThere are many libraries out there to help you do that. What I've come to enjoy is [structopt]: It gives you the power to annotate a struct or enum and turn its fields/variants into CLI flags:\n\nThis is _very_ concise - but also very powerful! (It uses [clap] behind the scenes.)\n\nOr:\n\nError handling\n\n_This was updated April 2020._\n\nIn many CLI applications, error handling doesn't have to be complicated.\nAll you need is a library like [anyhow]\nthat let's you bubble up basically all error types\nand optionally add some context (descriptions) to them.\n\n[anyhow]: https://docs.rs/anyhow/1.0.28/anyhow/\n\nThis looks easy enough, right?\nThere's a lot of ways to enhance the way you deal with errors,\nfor example by writing your own error types.\nBut for quick CLI tools, this is most often not necessary.\n\nBy th way,\nhere is what the error output looks like:\n\nOr:\n\nMany small crates\n\nDon't be afraid to depend on a lot of crates. Cargo is really good at allowing you not to care about compiling and updating dependency, so let Cargo, and the Rust community, help you!\n\nFor example, I recently wrote a CLI tool with 37 lines of code. This is the first block:\n\n_Note from the future (April 2020): Since the introduction of Rust 2018, you don't need to write extern crate anymore. Yay!_\n\nMany small helper functions\n\nI tend to write a lot of small functions. Here's an example of such a \"small helper function\":\n\nOkay, that's a bit underwhelming. How about this one?\n\nIt's one level more abstract than the standard library, hides an allocation of a String with unknown length, but… it's really handy.\n\nI know could put the function bodies just inside the main code, but giving these little code blocks names and getting the option of reusing them is really powerful. It also makes the main function a tad more abstract and easier to read (no need to see through all the implementation details). Furthermore (but this tends to not really shine in small CLI apps) it makes things easily unit-testable.\n\nAnd by the way: In most small CLI tools, performance is not that important. Feel free to prefer .clone() to sprinkling your code with lifetime parameters.\n\nLots of structs\n\nIn my experience, it really pays off to use a lot of structs. Some scenarios:\n\n- Have the choice between using serde_json::Value with a bunch of matches or a struct with #[derive(Deserialize)]? Choose the struct, get performance, nice errors, and documentation about the shape you expect.\n- Pass the same 3 parameters to a bunch of functions? Group them in a (tuple) struct, give the group a name, and maybe even turn some of these functions into methods.\n- See yourself writing a lot of boilerplate code? See if you can write a struct/enum and use a derive.\n\nBonus: Logging\n\nAnd a bonus round: Some logging with [loggerv]! (It's really simple, but usually suffices for CLI apps. No need to go all in with streaming JSON logs to logstash for now.)\n\nSweet! Let's run it three time with more of less verbosity!\n\nConclusion\n\nThese were my five tips for writing small CLI applications in Rust (writing nice libraries is [another topic][elegant APIs]). If you have more tips, let me know!\n\nIf you want to dig a little deeper, I'd suggest looking at how to [multi-platform build Rust binaries releases][trust], how to use [clap] to get [autocompletion for CLI args], and how to write integration test for your CLI apps (upcoming post).\n\n[Rust]: https://www.rust-lang.org/\n[structopt]: https://docs.rs/structopt\n[clap]: https://clap.rs\n[error-chain]: https://docs.rs/error-chain\n[failure]: https://boats.gitlab.io/failure/\n[cargo-expand]: https://crates.io/crates/cargo-expand\n[loggerv]: https://docs.rs/loggerv\n[elegant APIs]: /elegant-apis-in-rust.html\n[trust]: https://github.com/japaric/trust\n[autocompletion for CLI args]: https://blog.clap.rs/complete-me/",
"canonicalUrl": "https://deterministic.space//rust-cli-tips.html"
}