To-Do from CLI with Rust

ShoeheyOt - Sep 12 - - Dev Community

Introduction

Hey there 👋
This article is about how I challenged new thing, learned its principal features, and succeeded less than a month.
As a software developer, I am always excited to try new technologies. Recently I decided to build a command-line to-do list application using Rust, despite having no prior experience with the language.

I had been looking for a simple, light and fast to-do app while coding and decided to create one by myself. Since I have used some services made with Rust,(like a code editor, formatter,etc.) and I know how fast they are. So I decided to use Rust.

I believe this project not only pushed up my skill but also showcased my ability to quickly adapt to new technologies.

Background

I started learning Rust less than a month ago, 100% self-studying using materials which are all made by Rust Official.

  • Official documentation
  • Rust By Example
  • Rustling hands-on course Rust recommended materials

Project Overview

Nothing fancy in terms of features, you can display all remained tasks, add or delete a task.
The point is that all operations are done from a terminal.

CLI Example

Type one of the number from 1 to 4 for operation you desire e.g. Type 1 and display remained task.

Implementation

I utilize match { } inside loop{ } as flow of control for infinite loop unless user's input is 4, using module std::io to detecte user input.

 let mut input = String::new();
 io::stdin().read_line(&mut input)?;

loop{

 match input.trim(){
   "1" => {
     some_oparation();

     continue,  
   },

   "2" => {
     .
     .
     .

   "4" => {
    end_oparation();

     break;
   },
   _ => {
    fall_back();

    continue;
   }

}
Enter fullscreen mode Exit fullscreen mode

For now, the todo items are stored in a text file(if it doesn't exist, new txt file will be created).

What I succeeded and learned

I successfuly implemeneted basic feature of to-do from terminal.
It was a good experience to create with Rust. The thing I struggled with most of the time is ownership/borrowing.

Owenership

Ownership is a core concept and a set of rule in Rust that governs how memory is managed. Each value has an owner that is responsible for deallocating the value when it is no longer needed. There can be only one owner to each value. When the owner of a value goes out of scope, the value will be dropped.

//example 1 Ownership
{                        //s is invalid at this line 
                         //as it is not decleared yet

  let s = "hello";       // s owns the string "hello" and s is now valid

  let new_s = s;         // the ownership of the string "hello" is transfered 
                         //to variable new_s and s is no longer valid

}                        // this is end of scope and new_s is not valid anymore
Enter fullscreen mode Exit fullscreen mode

*scope is block {}

Borrowing

Borrowing is a Rust's feature which lets you use values without taking ownership. You can use this by adding symbol & before variable.

//example 2 Borrowing
  fn main () {
      let s = "hello";

      print_string(&s);   

      print!("{}", s);   // still available to use variable `s`
  }

  fn print_string (s:&str) {
      print!("{} world", s); //display hello world in terminal
  }
Enter fullscreen mode Exit fullscreen mode

in example 2, &s means borrowing the value of s not taking ownership of it.
Not like example 1, variable s is not reassigned to an another one, so it seems s is still able to be used in print!()(BTW this is same as console.log() in Javascript/Typescript) without additional ampersands. It is needed. Passing a variable(not borrowing) to a function is similar to reassigning, so if you still need to use variable s in same scope, one way it to pass a variable as reference to a function print_string()

Mutability

The default behavior of variable in Rust is immutable which is good in point of safety and concurrency, so what if you want to update the value in a variable like Example 3?


//Example 3 Mutability
 fn main(){
  let s = "hello";

  print("{}", s);

  s = "goodbye";

  print("{}", s);
 }

Enter fullscreen mode Exit fullscreen mode

Example 3 is invalid. If you cargo run(npm run in Javascript) with the example 3, it would dislplay below error message.


error[E0384]: cannot assign twice to immutable variable `s`
 --> src/main.rs:3:5
  |
2 |     let s = "hello";
  |         -
  |         |
  |         first assignment to `s`
  |         help: consider making this binding mutable: `mut s`
6 |     s = "goodbye";
  |     ^^^^^^^^^^^^^ cannot assign twice to immutable variable

For more information about this error, try `rustc --explain E0384`.
warning: *** generated 1 warning
error: could not compile *** due to 1 previous error; 1 warning emitted


Enter fullscreen mode Exit fullscreen mode

You might notice within error message, it sometimes gives us hint which saved me time during the project.(Be careful, it is just a hint, not an answer)

To fix the issue, you need to explicity tell Rust this variable is mutable using keyword mut like below example 3-1


//Example 3-1 Mutability
 fn main(){
  let mut s = "hello";

  print("{}", s);

  s = "goodbye";

  print("{}", s);
 }

Enter fullscreen mode Exit fullscreen mode

Why it is needed - Ownership and Borrowing

All programs have to manage how to use memory of computer while running. Some languages have garbage collector that regulary check memory which is no longer used while running. In Rust, memory is managed through this ownership concept. At the closing curly brackets, special function called drop is automatically called. This is the timing where the owner of value returns the memory.
So no garbage collectoer or explicitly free memory execution is needed, it automatically frees memory when variables goes out of scope. This concept of Rust make it fast, memory-efficient and memory-safety yet flexible language.

Improvements

There are two things I would like to improve

Editing feature

The current features are adding, reading, and deleting, but you might forget which tasks are in progress and which are not if there are more than you can remember. It is nice to see progress, so I am planning to add editing feature so that a user can check whether remained tasks are in progress or haven't been started yet.

Refactor

With current implementation, a newly created task is saved to text file which is located in the root of working space(if not exist, new file would be created), which is not ideal in performance, scalability or data integrity perspectives. Instead it is better to implement storing data in database, like PostgreSQL or mySQL and I will change it soon.

Conclusion

Through this my first Rust project, even it is simple one, I felt how safe and fast the language is, and not 'that' hard to learn and create something with it. I was thinking this language was not for me, too difficult to learn until I started this project. If you haven't done anything with it and are looking for something new to learn, I would recommend Rust.

Thank you for reading so far, if you have any question or suggestion, please leave a comment or contanct me via Linkedin.

Github Repo
LinkedIn

Reference

. . . .
Terabox Video Player