Journey in Rust: Building a Command Line Tool - Part 1

Journey in Rust: Building a Command Line Tool - Part 1

Hey there, Rusty folks! πŸ™Œ As you may know, I've been diving headfirst into the magical land of Rust, and I've decided to share my journey with you as I build a command line tool! Let's get ready to rumble, shall we? πŸš€

Step 0: Say "Hello" to the World

Alright, let's start with the basics - the "Hello World!" program. You know, like anyone starting with a new programming language! Here's a simple one-liner:

cargo new termoil
cd termoil

Now inside the termoil directory, you'll find a src folder with a main.rs file. Open it up and add this code snippet:

fn main(){
    println!("Hello world!");
}

This is like magic, isn't it? πŸ’« We're using println! for the first time. It's like shouting "Hello World!" from the top of a mountain, just without the echo. It's a macro to print stuff to the console. If you're wondering why there's an exclamation mark, that's Rust's way of saying "Hey, I'm a macro, not a function!" πŸ“£

Now, we've got our very basic Rust program. But we need to make it more useful, right?

Step 1: Taking user input using std library

Our first mission is figuring out how to take user input. Imagine it like being at a party and trying to get someone's attention. After doing some research, I found that the std library can help me do just that! Let's add this code snippet to our main.rs file:

fn main() {
    let arguments = std::env::args().collect::<Vec<String>>();
    println!("{:?}", arguments);
}

Here's a quick breakdown of the code:

  • std::env::args() gives us an iterator over the command line arguments (like a conga line of words waiting to be heard).

  • We then use collect::<Vec<String>>() to collect the arguments into a Vec<String> (imagine it as herding those conga-dancing words into a cozy little word pen).

  • Finally, we print out the collected arguments using println!("{:?}", arguments);.

Run the code and see what happens!

$ cargo run -- hello world
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/rusty hello world`
["target/debug/rusty", "hello", "world"]

Step 2: Realizing the limitations of std library for CLI apps

Alright, so using the std library for taking user input is decent enough. But it's not quite the best choice for CLI apps. If we stick with it, we'll have to do a lot of manual work (akin to making a delicious smoothie with just a spoon - possible but far from ideal). So let's bring out the big guns and switch to a more powerful option: the clap crate!

Add this dependency to your Cargo.toml file:

What is cargo.toml?
Cargo.toml is like the map and compass of your Rust project. It's a configuration file written in TOML (Tom's Obvious, Minimal Language) format that helps you manage your project's dependencies, metadata, and build settings. It's where you declare essential information about your project and any additional crates (libraries) you want to use.

[dependencies]
clap = { version = "4.2.1", features = ["derive"] }

This tells Cargo (Rust's package manager) that our project relies on the clap crate with version 4.2.1 and the "derive" feature enabled. When we build or run our project, Cargo will automatically download and compile the clap crate and make it available for us to use in our code.

Think of Cargo.toml as a treasure map that guides you through the Rust ecosystem, helping you find all the powerful tools and libraries you need for your coding adventure! πŸ—ΊοΈπŸ’Ž

Step 3: Switching to clap for better user input handling

Now that we have clap ready to roll, let's update our main.rs file:

use clap::Parser;

#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
    // query the user wants to enter
    #[arg(short, long)]
    query: String,
}

fn main() {
    let arguments = Args::parse();
    println!("Query: {:?}", arguments.query);
}

Let's dissect the changes like a biologist examining a fascinating new species:

  • We've imported the Parser trait from clap with use clap::Parser;.

  • Now, structs in Rust are like customized containers that can store different types of data (picture a lunchbox designed to hold your favorite snacks). So we created a new struct called Args and derived the Parser and Debug traits for it.

  • Inside the struct, we defined a field named query with type String. This will hold the user's input (like storing their attention-grabbing party phrase in our tailor-made lunchbox).

  • We used the attribute #[arg(short, long)] to tell clap that this field is an argument. Here's what it means:

    • The short attribute tells clap to accept a single hyphen followed by a single character as a short version of the argument (e.g., -q).

    • The long attribute accepts a double hyphen followed by the full argument name (e.g., --query). Both of these attributes make it easier for users to provide input without typing the whole thing out.

  • In the main function, we replaced our previous user input handling code with let arguments = Args::parse();. Think of it as upgrading from a spoon to an actual blender for our smoothie-making process - way more efficient!

  • Lastly, we print out the parsed query with println!("Query: {:?}", arguments.query);.

Additional information:

  • Traits: A trait is like a template of behavior that can be shared among different types. It's a way to define certain methods or functionalities that a type must implement. Think of it as a blueprint for a robot with defined abilities (like walking or talking) that can be inherited by actual robots.

  • Parser trait: When we derive the Parser trait for our Args struct, we're telling Rust that we want the Args struct to have all the parsing powers that come with the Parser trait. This allows us to parse command-line arguments according to our desired structure and makes handling user input much more convenient.

Run the code again and bask in the glory of using clap for your CLI app!

$ cargo run -- --query hello
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/rusty --query hello`
Query: "hello"

And there you have it, my fellow explorers! We journeyed through the forest of user input handling, going from the standard library to upgrading our tools with the mighty clap crate. Remember, the right tools make all the difference when venturing into the wild world of programming! 🌟 Happy coding!πŸ˜‰

Cover: Bhupesh

πŸ”— Repository: termoil

Β