Tech News
← Back to articles

Rust CLI with Clap

read original related products more articles

Types Define Interfaces

Types are important. In fact, I'd guess that the expressive type system in rust is the single biggest reason why so many developers love the language. Types allow us to have a contract between parts of the system about our data and how to interact with it. All programming languages have the concept of types, but these exist along several dimensions. Strongly typed vs weakly typed as well as static vs dynamic typing. Rust stakes out its place as a statically, strongly typed language.

Many languages that are go-to solutions for creating custom command line tools fall in the opposite quadrant with weak, dynamic typing. Whether looking at currently popular tooling like python and node.js or more traditional solutions like awk and perl, they tend to favor a loose approach to types. Perhaps this is the result of an iterative approach to designing CLI tools that might favor flexibility. Or it could just be that those languages are already popular, leading to an abundance of such programs. Regardless of the reasons, I feel that there is tremendous value for both the developer and user which can arise from interacting with the command line via the sort of strict contract that rust's type system enables.

Note I assume that if you're already a rust developer, or at least rust-curious, then I don't need to convince you of the general value of strong, static typing. Rather, this is a call to use this same approach for interacting with a command line user as you would when developing a library or service API.

How Programs Interact with the Command Line

At the very lowest level rust exposes command line arguments through the std::env::args function that returns an Args struct, an Iterator for the String arguments passed to start the program. This is illustrated in the Rust Book's section on accepting command line arguments:

1 use std :: env ; 2 3 fn main () { 4 let args : Vec < String > = env :: args (). collect (); 5 6 let query = & args [ 1 ]; 7 let file_path = & args [ 2 ]; 8 9 println! ( "Searching for {query} " ); 10 println! ( "In file {file_path} " ); 11 }

The naive approach seen above obviously lacks robustness as it relies entirely on argument positioning and also makes a number of other assumptions about the results. Perhaps for very simple tools this solution can work but as the number and types of arguments increases, it seems unlikely that a developer would want to try and rely on just argument position for the interface to their program.

A more flexible approach would be to examine all of the arguments passed in and parse these for patterns that would allow customary -x and --x_long style options. Doing this by hand for every CLI tool would be error-prone and tedious, but fortunately some awesome folks have already done that for you with the excellent clap crate.

The Sound of One Hand Clapping

... continue reading