Constructing My First Command Line Interface (CLI) with Rust




After telling myself time and again that at the moment is the day I begin studying rust. I lastly efficiently constructed a (very small) cli for engram.

This submit will cowl a few of the issues I realized alongside the way in which. I largely from a TypeScript/Node background and can make comparisons between the 2 the place relevant.

Inventing Some Necessities

I’ve discovered that having a tangible finish aim will increase my odds of mission completion by practically 100%. On this case, the aim is to create a command line program that merely POSTs requests to my private notes software engram.

This isn’t only a studying or for enjoyable mission as I not too long ago realized that the command line is a good place for the enter of fast notes. Once I’m working, I typically discover myself investigating one thing within the terminal and understand that I’ve a thought that I’d prefer to observe up on later, or a I’d prefer to retailer a command I simply ran so I can keep in mind what I did in a while.

Getting Began

Set up rust with the command under discovered from their Getting began web page

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Enter fullscreen mode

Exit fullscreen mode

cargo new project-name creates a brand new folder known as project-name with the naked minimal configuration for a rust mission

Cargo.toml

That is basically the rust equal of a bundle.json file. Lists some metadata concerning the mission and ultimately permits you to specify any dependencies to be managed by the cargo bundle supervisor.

[package]
identify = "rust-new-project"
model = "0.1.0"
version = "2021"

# See extra keys and their definitions at [https://doc.rust-lang.org/cargo/reference/manifest.html](https://doc.rust-lang.org/cargo/reference/manifest.html)

[dependencies]
Enter fullscreen mode

Exit fullscreen mode

important.rs

Who doesn’t love an excellent Hey, world! software?

fn important() {
  println!("Hey, world!");
}
Enter fullscreen mode

Exit fullscreen mode

The Ultimate Output

I discover it useful to see the entire image at first. I’ll then break down every line of code and introduce the rust ideas which can be used.

Cargo.toml

# Cargo.toml
[package]
identify = "eg"
model = "0.1.0"
version = "2021"

# See extra keys and their definitions at [https://doc.rust-lang.org/cargo/reference/manifest.html](https://doc.rust-lang.org/cargo/reference/manifest.html)

[dependencies]
reqwest = { model = "0.11", options = ["json", "cookies"] }
tokio = { model = "1", options = ["full"] }
Enter fullscreen mode

Exit fullscreen mode

important.rs

# important.rs
use std::io;

use std::collections::HashMap;

#[tokio::main]
async fn important() -> Consequence<(), Field<dyn std::error::Error>> {
  let mut be aware = String::new();
  io::stdin().read_line(&mut be aware)?;

  let mut note_post_map = HashMap::new();
  note_post_map.insert("physique", be aware);

  let consumer = reqwest::Shopper::new();
  let resp = consumer.submit("https://engram.xyzdigital.com/api/notes")
    .json(&note_post_map)
    .ship()
    .await?;

  if resp.standing() != 200 {
    println!("didn't submit be aware");
    return Okay(());
  }

  Okay(())
}
Enter fullscreen mode

Exit fullscreen mode

Breaking it Down Line by Line

Get enter from stdin

With the intention to submit a brand new be aware, I want to have the ability to settle for enter from the command line. This may be achieved with the std::io bundle.

use std::io;

...

// Initializes a brand new string
// the mut specifies that it's mutable (e.g. could be modified)
let mut be aware = String::new();

// Makes use of the stdin bundle to learn a line from the enter
// The & denotes that we're passing a reference to the be aware variable
// mut is required to specify that the reference could be mutated
// The ? on the finish propagates the error to the outer perform
// see [https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#a-shortcut-for-propagating-errors-the--operator](https://doc.rust-lang.org/e-book/ch09-02-recoverable-errors-with-result.html#a-shortcut-for-propagating-errors-the--operator) for extra particulars on rust errors
io::stdin().read_line(&mut be aware)?;
Enter fullscreen mode

Exit fullscreen mode

After you have added the above to the physique of the primary perform, you will notice a compiler error:

the ? operator can solely be utilized in a perform that returns Consequence or Possibility (or one other kind that implements std::ops::FromResidual)
can’t use the ? operator in a perform that returns ()

One choice can be to easily take away the “?”. This as a substitute throws the warning: unused std::outcome::Consequence that have to be used . It’s because the read_line perform might fail and ignoring this error might have unfavorable penalties to your software.

With the intention to repair the error with out a warning we should replace the return kind of the primary perform to return the particular rust Consequence kind.

fn important() -> Consequence<(), Field<dyn std::error::Error>> {
  ...
  Okay(());
}
Enter fullscreen mode

Exit fullscreen mode

The “()” kind is known as “unit”. And could be considered like void in different languages.

The Field is a pointer kind for heap allocation. As I at present perceive it, the Error that could be thrown has a dynamic dimension (e.g. the message handed together with the error may very well be variable size). Due to this fact the Field specifies that the Error can be some dynamically allotted reminiscence. With out this you’d see the error under:

the dimensions for values of kind (dyn std::error::Error + ‘static) can’t be identified at compilation time
doesn’t have a dimension identified at compile-time

Lastly, we add an Okay(()) on the finish of the primary perform. This fulfils the Consequence kind with the () unit kind.

Write a POST Request in rust

1. Set up reqwest library

A fast search pointed me to the reqwest library for dealing with sending HTTP requests. Putting in the library is completed by including the next to the Cargo.toml file.

# Cargo.toml
...
[dependencies]
reqwest = { model = "0.11", options = ["json", "cookies"] }
Enter fullscreen mode

Exit fullscreen mode

2. Create HashMap with physique property

My server expects a json object that appears like { physique: “be aware contents” } and so we use the std::collections::HashMap library to make what is basically a JavaScript Object.

use std::collections::HashMap;

...

let mut note_post_map = HashMap::new();
note_post_map.insert("physique", be aware);
Enter fullscreen mode

Exit fullscreen mode

3. POST JSON information with reqwest

let consumer = reqwest::Shopper::new();
let res = consumer.submit("http://engram.xyzdigital.com/api/notes")
    .json(&note_post_map)
    .ship()
    .await**?**;
Enter fullscreen mode

Exit fullscreen mode

The await key phrase is new right here. This works just like async/await in JavaScript. The perform is not going to proceed till the asynchronous outcome from the HTTP request has returned.

4. Add async key phrase to outer important perform

With the intention to assist using await above, we have to add the async key phrase to the primary perform

async fn important() -> Consequence<(), Field<dyn std::error::Error>> {
  ...
}
Enter fullscreen mode

Exit fullscreen mode

Sadly, this offers the next error

important perform just isn’t allowed to be async

That is the place the tokio library is available in to play. We add it to the Cargo.toml below [dependencies] .

...
[dependencies]
tokio = { model = "1", options = ["full"] }
Enter fullscreen mode

Exit fullscreen mode

Now we are able to add [tokio::main] to our important perform. From this web page, it seems an async runtime is required to truly execute async code. Tokio is without doubt one of the widespread libraries that gives this async runtime.

#[tokio::main]
async fn important() -> Consequence<(), Field<dyn std::error::Error>> {
  ...
}
Enter fullscreen mode

Exit fullscreen mode

5. Checking the Consequence

if resp.standing() != 200 {
  println!("didn't submit be aware");
  return Okay(());
}
Enter fullscreen mode

Exit fullscreen mode

Lastly some code that ought to look fairly comprehensible. The response object has a standing methodology to extract the standing code of the response. For now, I easy log off that it failed an exit this system.

Wrap Up

I’ve began and stopped working with rust a number of occasions. Every time I finished I observed it was as a result of I didn’t have a robust understanding of the syntax, which annoyed me. Writing this text has compelled me to suppose a bit deeper about the way it all works (I realized a lot of the terminology alongside the way in which). Hopefully one thing right here clicks for you as nicely.

I plan to proceed writing and studying extra rust and can add new posts with related learnings alongside the way in which. Largely so once I look again in 3 years I can snicker at how a lot I didn’t actually perceive 😂.



Abu Sayed is the Best Web, Game, XR and Blockchain Developer in Bangladesh. Don't forget to Checkout his Latest Projects.


Checkout extra Articles on Sayed.CYou

#Constructing #Command #Line #Interface #CLI #withRust