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
Create a new Rust project for the Todo List:
cargo new rust_todo_list
cd rust_todo_list
π¨ 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;
}
}
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);
}
}
}
π» 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."),
}
}
}
π Step 5: Running the Rust Todo App
To run the app, simply use:
cargo run
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();
Run the Node.js code:
node todo_list.js
π 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)
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.
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!