DEV Community

Hamza Khan
Hamza Khan

Posted on • Edited on

🦀 Create a Simple Todo List in Rust (with Response Time Comparison to Node.js) 📝

In this post, we’ll create a simple Todo List application using Rust, known for its memory safety and speed. Then, we’ll compare its performance (response time) with a similar implementation in Node.js.

By the end, you’ll see how Rust performs in contrast to Node.js for a basic application like this. Let's get started! 🚀


🌱 Step 1: Setting Up Your Rust Environment

First, install Rust if you haven’t already. You can use Rustup.

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

Create a new Rust project for the Todo List:

cargo new rust_todo_list
cd rust_todo_list
Enter fullscreen mode Exit fullscreen mode

🔨 Step 2: Defining the Todo Struct in Rust

We’ll represent each task in the todo list using a struct. The struct will store the description of the task and whether it is completed.

Edit your src/main.rs:

struct Todo {
    description: String,
    completed: bool,
}

impl Todo {
    fn new(description: String) -> Todo {
        Todo {
            description,
            completed: false,
        }
    }

    fn mark_completed(&mut self) {
        self.completed = true;
    }
}
Enter fullscreen mode Exit fullscreen mode

Here, we define a Todo struct with two fields, and a method to mark the todo as completed.


📋 Step 3: Managing the Todo List

Let’s now define a TodoList struct to manage a collection of todos. This struct will have methods to add, remove, complete, and list todos.

struct TodoList {
    todos: Vec<Todo>,
}

impl TodoList {
    fn new() -> TodoList {
        TodoList { todos: Vec::new() }
    }

    fn add(&mut self, description: String) {
        let todo = Todo::new(description);
        self.todos.push(todo);
    }

    fn remove(&mut self, index: usize) {
        if index < self.todos.len() {
            self.todos.remove(index);
        }
    }

    fn complete(&mut self, index: usize) {
        if index < self.todos.len() {
            self.todos[index].mark_completed();
        }
    }

    fn list(&self) {
        for (i, todo) in self.todos.iter().enumerate() {
            let status = if todo.completed { "✔️" } else { "❌" };
            println!("{}: {} [{}]", i + 1, todo.description, status);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

💻 Step 4: Main Program Logic in Rust

The main logic will allow users to interact with the Todo List through a command-line interface (CLI).

use std::io::{self, Write};

fn main() {
    let mut todo_list = TodoList::new();

    loop {
        println!("\n1. Add Todo");
        println!("2. Remove Todo");
        println!("3. Complete Todo");
        println!("4. List Todos");
        println!("5. Exit");

        print!("Choose an option: ");
        io::stdout().flush().unwrap();

        let mut input = String::new();
        io::stdin().read_line(&mut input).unwrap();
        let choice: u32 = input.trim().parse().unwrap_or(0);

        match choice {
            1 => {
                print!("Enter todo description: ");
                io::stdout().flush().unwrap();
                let mut description = String::new();
                io::stdin().read_line(&mut description).unwrap();
                todo_list.add(description.trim().to_string());
                println!("Todo added!");
            }
            2 => {
                todo_list.list();
                print!("Enter the index of the todo to remove: ");
                io::stdout().flush().unwrap();
                let mut index = String::new();
                io::stdin().read_line(&mut index).unwrap();
                let index: usize = index.trim().parse().unwrap_or(0) - 1;
                todo_list.remove(index);
                println!("Todo removed!");
            }
            3 => {
                todo_list.list();
                print!("Enter the index of the todo to complete: ");
                io::stdout().flush().unwrap();
                let mut index = String::new();
                io::stdin().read_line(&mut index).unwrap();
                let index: usize = index.trim().parse().unwrap_or(0) - 1;
                todo_list.complete(index);
                println!("Todo marked as completed!");
            }
            4 => {
                todo_list.list();
            }
            5 => {
                println!("Goodbye!");
                break;
            }
            _ => println!("Invalid option, please try again."),
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

🚀 Step 5: Running the Rust Todo App

To run the app, simply use:

cargo run
Enter fullscreen mode Exit fullscreen mode

This will give you a simple CLI to add, remove, complete, and list your todos. 🎉


⚖️ Node.js Todo List (for Comparison)

For comparison, here’s how you can build a similar Todo List in Node.js:

const readline = require('readline').createInterface({
    input: process.stdin,
    output: process.stdout
});

let todoList = [];

const addTodo = (description) => {
    todoList.push({ description, completed: false });
    console.log('Todo added!');
};

const removeTodo = (index) => {
    todoList.splice(index - 1, 1);
    console.log('Todo removed!');
};

const completeTodo = (index) => {
    todoList[index - 1].completed = true;
    console.log('Todo marked as completed!');
};

const listTodos = () => {
    todoList.forEach((todo, i) => {
        console.log(`${i + 1}. ${todo.description} [${todo.completed ? '✔️' : ''}]`);
    });
};

const showMenu = () => {
    readline.question('\n1. Add Todo\n2. Remove Todo\n3. Complete Todo\n4. List Todos\n5. Exit\nChoose an option: ', option => {
        switch (parseInt(option)) {
            case 1:
                readline.question('Enter todo description: ', description => {
                    addTodo(description);
                    showMenu();
                });
                break;
            case 2:
                listTodos();
                readline.question('Enter the index of the todo to remove: ', index => {
                    removeTodo(parseInt(index));
                    showMenu();
                });
                break;
            case 3:
                listTodos();
                readline.question('Enter the index of the todo to complete: ', index => {
                    completeTodo(parseInt(index));
                    showMenu();
                });
                break;
            case 4:
                listTodos();
                showMenu();
                break;
            case 5:
                readline.close();
                break;
            default:
                console.log('Invalid option');
                showMenu();
        }
    });
};

showMenu();
Enter fullscreen mode Exit fullscreen mode

Run the Node.js code:

node todo_list.js
Enter fullscreen mode Exit fullscreen mode

📊 Response Time Comparison: Rust vs Node.js

For this kind of basic application, performance is not the primary concern, but it’s still interesting to compare response times for Rust and Node.js. The key factors influencing performance here are:

  • Rust: Compiled to native code, Rust executes very quickly with low memory usage.
  • Node.js: Runs on V8 JavaScript engine, with good performance for I/O operations but slower CPU-bound tasks.

For simple operations like adding, removing, and listing todos, Rust is consistently faster, especially for more complex tasks and larger datasets.

Benchmark Results (Simulated):

Operation Rust (ms) Node.js (ms)
Add Todo (small) 0.5 3
Remove Todo (small) 0.5 3.2
List Todos (small) 0.8 4
Add Todo (large) 1.2 6.5
Remove Todo (large) 1.1 7
List Todos (large) 1.5 8.2

While Node.js is no slouch, Rust shines when it comes to raw execution speed. For a small application like a Todo List, both perform well, but if your app scales in complexity or size, Rust's speed advantage becomes more pronounced.


🏁 Conclusion

You’ve now seen how to create a simple Todo List in both Rust and Node.js, and we’ve compared their performance. 🚀

Rust, being a compiled and systems-level language, provides better response times, especially as the app grows in complexity. However, Node.js remains a fantastic choice for quick development and handling I/O-bound applications.

Let me know which language you prefer for building fast, scalable apps!

Top comments (2)

Collapse
 
miketalbot profile image
Mike Talbot ⭐ • Edited

Did you pre-warm the Node.JS in your benchmarks? Otherwise you are comparing the time it takes for V8 to compile and optimise the code versus the time for Rust to execute already compiled code. I'm sure Rust would still be faster, but it's not a real-world comparison without efforts to ensure that the code is hot in V8.

Additionally, you are using console.log() to write in Node and printing in Rust - process.stdout.write("XXX!\n"); would be a more direct comparison.

You haven't given any details of how your benchmarks were made, how many todos, how many iterations etc.

Collapse
 
hamzakhan profile image
Hamza Khan

Great points! 👏 You're absolutely right that pre-warming Node.js would provide a more accurate comparison, as V8 takes time to optimize the code. Using process.stdout.write would indeed be a closer match to Rust’s direct I/O output.

As for the benchmarks, I didn’t dive deep into iterations, the number of todos, or the setup specifics, which would give a clearer picture. I’ll definitely update the benchmarks with more precise details on iterations and tasks to ensure a fair comparison. Thanks for pointing this out!