Rustlers Molecule 1A: ToDo CLI Application
April 18, 2026•851 words
This molecule stitches together only the atoms 1.1–1.10. No structs, no macros beyond formatting, no external crates. You’ll see how far you can get with just: expressions and statements (1.1), scalars (1.2), tuples/arrays/slices (1.3), bindings and mutability (1.4), if / match (1.5), loops (1.6), functions (1.7), &str vs String (1.8), conversions (1.9), and formatting (1.10).
We’ll build a single-file program:
- Stores tasks in
todo.txtin the current directory (simple persistence). - Commands:
add <text>,list,done <id>,del <id>,clear,help. - File format is deliberately simple: each line is
"0|text"(not done) or"1|text"(done). - We use functions for each operation, match for the CLI dispatch,loops (1.6) to read/write lines, and formatting for clean output.
use std::env;
use std::fs;
use std::io;
use std::path::Path;
const FILE_NAME: &str = "todo.txt";
// This tuple represent a task
type Task = (bool, String);
// This function loads a list of tasks from file
fn load_tasks() -> io::Result<Vec<Task>> {
// Check if the file exist, otherwise it creates
// an empty one
if !Path::new(FILE_NAME).exists() {
return Ok(Vec::new());
}
// Read the file concent
let content = fs::read_to_string(FILE_NAME)?;
// Create the file vector
let mut tasks = Vec::new();
for line in content.lines() {
// Expected: "0|text" or "1|text"
let mut parts = line.splitn(2, '|');
let status = parts.next();
let text = parts.next();
let parsed = match (status, text) {
(Some("0"), Some(t)) => (false, t.to_string()),
(Some("1"), Some(t)) => (true, t.to_string()),
_ => continue, // skip malformed lines
};
tasks.push(parsed);
}
Ok(tasks)
}
// Function that saves a list of tasks to a file,
// returning the result of the operation
fn save_tasks(tasks: &[Task]) -> io::Result<()> {
let mut out = String::new();
for &(done, ref text) in tasks {
let flag = if done { '1' } else { '0' };
out.push_str(&format!("{}|{}\n", flag, text));
}
fs::write(FILE_NAME, out)
}
// Function that handles the 'list' command which lists
// the tasks present in the file
fn cmd_list() -> io::Result<()> {
let tasks = load_tasks()?;
if tasks.is_empty() {
println!("No tasks yet.");
return Ok(());
}
for i in 0..tasks.len() {
let (done, ref text) = tasks[i];
let box_char = if done { 'x' } else { ' ' };
println!("[{}] {:>3}: {}", box_char, i + 1, text);
}
Ok(())
}
// Function that handles the 'add' command which adds
// a new task to the file
fn cmd_add(words: &[String]) -> io::Result<()> {
if words.is_empty() {
eprintln!("Usage: add <text>");
return Ok(());
}
let mut text = String::new();
for (i, w) in words.iter().enumerate() {
if i > 0 { text.push(' '); }
text.push_str(w);
}
let mut tasks = load_tasks()?;
tasks.push((false, text));
save_tasks(&tasks)?;
println!("Added.");
Ok(())
}
// Function that handles the 'done' commant which mark as done
// a task present in the file, identifyed ID
fn cmd_done(id_str: &str) -> io::Result<()> {
let id: usize = match id_str.parse() {
Ok(n) => n,
Err(_) => { eprintln!("ID must be a positive number."); return Ok(()); }
};
if id == 0 {
eprintln!("IDs start at 1.");
return Ok(());
}
let mut tasks = load_tasks()?;
if id > tasks.len() {
eprintln!("No task with id {}.", id);
return Ok(());
}
let i = id - 1;
tasks[i].0 = true;
save_tasks(&tasks)?;
println!("Marked #{} as done.", id);
Ok(())
}
// Function that handles the 'del' command which deletes
// a task present in the file, identified by ID
fn cmd_del(id_str: &str) -> io::Result<()> {
let id: usize = match id_str.parse() {
Ok(n) => n,
Err(_) => { eprintln!("ID must be a positive number."); return Ok(()); }
};
if id == 0 {
eprintln!("IDs start at 1.");
return Ok(());
}
let tasks = load_tasks()?;
if id > tasks.len() {
eprintln!("No task with id {}.", id);
return Ok(());
}
let mut kept = Vec::new();
for i in 0..tasks.len() {
if i != id - 1 {
kept.push(tasks[i].clone());
}
}
save_tasks(&kept)?;
println!("Deleted #{}.", id);
Ok(())
}
// Function that handles the 'clear' command which deletes
// all tasks present in the file
fn cmd_clear() -> io::Result<()> {
save_tasks(&[])?;
println!("Cleared all tasks.");
Ok(())
}
// Helper function that prints documentation of all
// available commands
fn print_help() {
println!("Usage:");
println!(" list Show all tasks");
println!(" add <text> Add a new task");
println!(" done <id> Mark a task as done");
println!(" del <id> Delete a task");
println!(" clear Remove all tasks");
println!(" help Show this help");
}
fn main() {
// Collect all passed arguments
let args: Vec<String> = env::args().collect();
// Check the number of arguments
let result = if args.len() < 2 {
print_help();
Ok(())
} else {
// Match against all admitted arguments
match args[1].as_str() {
"list" => cmd_list(),
"add" => cmd_add(&args[2..]),
"done" => {
if args.len() < 3 { eprintln!("Usage: done <id>"); Ok(()) }
else { cmd_done(&args[2]) }
}
"del" | "delete" => {
if args.len() < 3 { eprintln!("Usage: del <id>"); Ok(()) }
else { cmd_del(&args[2]) }
}
"clear" => cmd_clear(),
"help" | "-h" | "--help" => { print_help(); Ok(()) }
other => { eprintln!("Unknown command: {}", other); print_help(); Ok(()) }
}
};
if let Err(e) = result {
eprintln!("{}", e);
}
}