1 Month Learning Rust - My First CLI Application

1 Month Learning Rust - My First CLI Application

Daily short news for you
  • Here! A question that I've been wondering about for ages, and until yesterday, everything was finally clarified.

    Normally, people use height: 100vh to set the height equal to the viewport of the screen. On computers, there's no issue, and even simulating the size of a smartphone works fine. But when opened on a phone, height: 100vh always exceeds the viewport. Huh!? What's going on???

    The explanation for this is that mobile devices have a different way of calculating the viewport compared to computers. It is often interfered with or affected by the address bar and navigation bar of the platform you are using. Therefore, if you want 100vh on mobile to truly equal the viewport, you need to take an additional step to reset the viewport.

    It's easy, first, you need to create a CSS variable --vh right in the script tag at the top of the page.

    function updateViewportHeight() { const viewportHeight = globalThis.innerHeight; document.documentElement.style.setProperty('--vh', `${viewportHeight * 0.01}px`); } updateViewportHeight(); window.addEventListener('resize', updateViewportHeight);

    Then instead of using height: 100vh, change it to height: calc(var(--vh, 1vh) * 100). And that's it.

    » Read more
  • Nowadays, how much memory will 1 million (1M) concurrent tasks consume? That is the question posed by hez2010, and he decided to find the answer by experimenting with a simple program in various programming languages: How Much Memory Do You Need in 2024 to Run 1 Million Concurrent Tasks?

    To summarize, Rust remains unbeatable, but the second position surprised me 😳

    » Read more
  • Is something coming? 😱🥶

    » Read more

The Problem

A year ago, I declared that I would learn Rust within a month. And the result is that the series of articles about the Rust learning process has not ended yet. Can this be considered a failure? I think not.

Programming languages are just tools to solve problems. Learning a new one can broaden our experience and help us solve problems more efficiently. Besides learning, I also have other tasks with higher priority, so I have to work on them first.

Many times, I wanted to give up, but the series of articles about Rust was still well-received by readers. I don't know why, because what I wrote was just a summary or some explanations of my understanding of Rust. It seems that what people want to see is a companion. Ah! At least there is someone learning Rust like me.

In the past year, we have learned most of the basic knowledge of Rust, including variables, data types, and basic data structures. The most difficult part - ownership - I think should be applied to a real project to encounter problems.

So, this is the time to do a practical exercise, applying what we have learned to create a command-line tool (CLI). But then I remembered that I also created a CLI application to create and upload thumbnail images in the article Using CLI to Increase Efficiency in Work.

The previous application solved the problem of processing image sizes and uploading them in bulk to the blog. Imagine you have an image, regardless of the format and size, and when you put it into the CLI, it will create a series of images with different sizes and upload them to R2 of Cloudflare.

But, if we bring all the features of the old CLI to the new one, it will take a lot of time, so in this short article, I will simplify it. I will ignore the image processing step and just upload the image to R2.

Let's start!

First, create a new Rust project using cargo, named img-cli.

$ cargo new img-cli

Run the program.

$ cargo run
   Compiling img-cli v0.1.0 (/Users/hoaitx/src/hoaitx/img-cli)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.35s
     Running `target/debug/img-cli`
Hello, world!

Now, we need to get the argument which is the path to the image to be uploaded. Something like this:

$ img-cli path_to_image

In Rust, the simplest way to get the arguments after the command is args.

fn main() {
    let args: Vec<String> = env::args().collect();
    let file_path = &args[1];
}

The API to upload the image to R2 uses a PUT request, with a binary body containing the image data. One thing to note is that the name of the image uploaded to R2 is extracted from the API path. Additionally, a secret parameter 'X-Custom' in the headers is needed to authenticate the upload.

First, write a function to read the image file data.

fn read_file(path: &str) -> Result<Vec<u8>, std::io::Error> {
    let mut file = File::open(path)?;
    let mut file_bytes = Vec::new();
    file.read_to_end(&mut file_bytes)?;
    Ok(file_bytes)
}

read_file takes a path to the image, returns the data as Vec<u8> if successful, or an error.

Next, write a function to upload the image to R2. I use the reqwest library to call the API. This library usually comes with tokio to handle asynchronous operations. For simplicity, we will use the blocking API of reqwest.

Install reqwest:

$ cargo add reqwest --features blocking

After calling the API, the result is a JSON containing the link to the uploaded image. We need to extract this link and print it out. To do this, we need to map the response data to a struct. Install serde and serde_json to handle JSON.

$ cargo add serde --features derive
$ cargo add serde_json

Declare a struct Response to map the API response.

#[derive(serde::Deserialize, serde::Serialize)]
struct Response {
    full: String,
}

Put everything together into a function up_file.

#[derive(serde::Deserialize, serde::Serialize)]
struct Response {
    full: String,
}

fn up_file(path: &str) -> Result<String, Box<dyn std::error::Error>> {
    let url = "https://static-img.2coffee.dev";
    let client = reqwest::blocking::Client::new();
    let file_path = path;

    // get the file name from the path
    let file_name = file_path
        .split('/')
        .last()
        .unwrap_or("unknown")
        .to_string();

    let file_bytes = read_file(file_path)?;

    let response = client
        .put(format!("{}/{}", url, file_name))
        .header("Content-Type", "application/x-binary")
        .header("X-Custom-Auth-Key", "XXX")
        .body(file_bytes)
        .send();

    let response_struct: Response =
        serde_json::from_str(response?.text()?.as_str())?;

    Ok(response_struct.full)
}

Put the up_file function into main.

fn main() {
    let args: Vec<String> = env::args().collect();
    let file_path = &args[1];
    let url = up_file(file_path);

    print!("{}", url.unwrap());
}

Run the program.

$ cargo run -- '/Users/hoaitx/Downloads/hoaitx.jpg'
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.20s
     Running `target/debug/rs /Users/hoaitx/Downloads/hoaitx.jpg`
https://static-img.2coffee.dev/hoaitx.jpg

It works!

In this article, we applied basic knowledge and used some libraries to call the API and handle the response. It's not easy when we start working on a real project. In future articles, we will move on to more advanced concepts in Rust.

Premium
Hello

5 profound lessons

Every product comes with stories. The success of others is an inspiration for many to follow. 5 lessons learned have changed me forever. How about you? Click now!

Every product comes with stories. The success of others is an inspiration for many to follow. 5 lessons learned have changed me forever. How about you? Click now!

View all

Subscribe to receive new article notifications

or
* The summary newsletter is sent every 1-2 weeks, cancel anytime.
Author

Hello, my name is Hoai - a developer who tells stories through writing ✍️ and creating products 🚀. With many years of programming experience, I have contributed to various products that bring value to users at my workplace as well as to myself. My hobbies include reading, writing, and researching... I created this blog with the mission of delivering quality articles to the readers of 2coffee.dev.Follow me through these channels LinkedIn, Facebook, Instagram, Telegram.

Did you find this article helpful?
NoYes

Comments (0)

Leave a comment...