Rust lottery app on Terminal

I am from Bangladesh ,Rust is basically non-existent here. And my environment is Termux on my phone. I have written this lottery app code,I will be glad if someone reviews it. It takes names from user,saves it,then picks up random names as lotteries,the user can also delete names. It's my first project that I have written myself from scratch.

use rand::RngExt; //Written for rand v0.10.2
use std::fs;
use std::io;
use std::path::Path;
const SAVE_NAMES: &str = "names.txt";

fn save_names(list: &[String]) {
    let content = list.join("\n");

    match fs::write(SAVE_NAMES, content) {
        Ok(_) => {}
        Err(e) => println!("Warning: Could not save names: {}", e),
    }
}
fn load_names() -> Vec<String> {
    let mut names: Vec<String> = Vec::new();

    // If the file doesn't exist yet, return an empty Vec
    if !Path::new(SAVE_NAMES).exists() {
        return names;
    }

    let content = match fs::read_to_string(SAVE_NAMES) {
        Ok(c) => c,
        Err(e) => {
            println!("Warning: Could not load names: {}", e);
            return names;
        }
    };

    for line in content.lines() {
        let line = line.trim();
        if line.is_empty() {
            continue;
        }
        names.push(line.to_string());
    }

    names
}

fn show_names(list: &[String]) {
    if list.is_empty() {
        println!("No names to show! Add some names first.");
        return;
    }
    println!("All names:");
    for (i, name) in list.iter().enumerate() {
        println!("{}. {}", i + 1, name);
    }
}

fn add_names(list: &mut Vec<String>) {
    loop {
        loop {
            println!("Enter the name to add or 0 to exit:");
            let mut name = String::new();
            io::stdin().read_line(&mut name).expect("Failed.");
            if name.trim() == "0" {
                break;
            }
            if name.trim() == "" {
                continue;
            }
            let trimmed = name.trim().to_string();
            if list.contains(&trimmed) {
                println!("Name already available.");
                continue;
            }
            list.push(trimmed);
        }
        println!("New name list:");
        for (i, name) in list.iter().enumerate() {
            println!("{}. {}", i + 1, name);
        }
        save_names(list);
        println!("Do you want to go back to the main menu?Write 'Yes' to go back.");
        let mut choice = String::new();
        io::stdin().read_line(&mut choice).expect("Failed");
        match choice.trim().to_lowercase().as_str() {
            "yes" | "y" => {
                println!("Going back to the main menu:");
                break;
            }
            _ => {
                continue;
            }
        }
    }
}

fn lottery(list: &[String]) {
    println!("Do lotteries here!(Repeat possible, wheel style)");
    show_names(list);
    if list.is_empty() {
        return;
    }
    loop {
        println!("How many names to pick? Enter the number:");
        let mut count_input = String::new();
        io::stdin().read_line(&mut count_input).expect("Failed");
        let count: usize = match count_input.trim().parse() {
            Ok(n) if n >= 1 && n <= list.len() => n,
            Ok(_) => {
                println!("Please enter a number between 1 and {}.", list.len());
                continue;
            }
            Err(_) => {
                println!("Error: Please enter a valid number.");
                continue;
            }
        };

        let mut picked: Vec<String> = Vec::new();
        let mut rng = rand::rng();
        for _ in 0..count {
            picked.push(list[rng.random_range(0..list.len())].clone());
        }
        for (i, name) in picked.iter().enumerate() {
            println!("{}. {}", i + 1, name);
        }
        println!("Do you want to go back to the main menu? Type 'yes' to go back:");
        let mut choice = String::new();
        io::stdin().read_line(&mut choice).expect("Failed");
        match choice.trim().to_lowercase().as_str() {
            "yes" | "y" => {
                println!("Going back to the main menu.");
                break;
            }
            _ => {
                continue;
            }
        }
    }
}
fn del_names(list: &mut Vec<String>) {
    if list.is_empty() {
        println!("No names to delete.");
        return;
    }
    show_names(list);
    
     loop {
        println!("Enter the name number to delete or q to exit:");
        let mut del_in = String::new();
        io::stdin().read_line(&mut del_in).expect("Failed");
        if del_in.trim() == "q" {
            break;
        }
        let delete: usize = match del_in.trim().parse() {
            Ok(n) if n > 0 && n <= list.len() => n,
            Ok(_) => {
                println!(
                    "Invalid number,enter a valid one beteen 1 and {}",
                    list.len()
                );
                continue;
            }
            Err(_) => {
                println!(
                    "Invalid input,enter a valid number between 1 and {}",
                    list.len()
                );
                continue;
            }
        };
        list.remove(delete - 1);
        println!("Updated list:");
        for i in 0..list.len() {
            println!("{}. {}", i + 1, list[i]);
        }
    }
    save_names(list);
}
fn main() {
    let mut list: Vec<String> = load_names();
    loop {
        println!("{}", "=".repeat(42));
        println!("          LOTTERY APP ");
        println!("{}", "=".repeat(42));
        println!("Here you can do lotteries!\n");
        println!("Choose an option:");
        println!("1. Add name");
        println!("2. See all names");
        println!("3. Do lotteries");
        println!("4. Delete names");
        println!("5. Exit");
        let mut choice_input = String::new();
        io::stdin().read_line(&mut choice_input).expect("Failed");
        let choice: usize = match choice_input.trim().parse() {
            Ok(n) => n,
            Err(_) => {
                println!("Error: Please enter a valid number.");
                continue;
            }
        };
        match choice {
            1 => add_names(&mut list),
            2 => show_names(&list),
            3 => lottery(&list),
            4 => del_names(&mut list),
            5 => {
                println!("Thank you for using the lottery app, goodbye!");
                break;
            }
            _ => {
                println!("Please choose a valid option between 1 to 5.");
                continue;
            }
        }
    }
}
  • the UX is still in need of attention. for example, when adding names i was able to enter the same name more than once; e.g....
How many names to pick? Enter the number:
2
1. boa
2. boa
Do you want to go back to the main menu? Type 'yes' to go back:
  • a couple of nitpicks. in a select block, you can use '|' to group OR alternatives; e.g...
Ok(n) if n > 0 && n <= list.len() => n,
Ok(_) | Err(_) => {
    println!(
        "Invalid input, enter a valid number between 1 and {}",
        list.len()
    );
    continue;
}

or even better...

_ => {
//     println!(
//         "Invalid number, enter a valid one beteen 1 and {}",
//         list.len()
//     );
//     continue;
// }
// Err(_) => {
    ...
}
  • in a block, when it consists of only one statement, you can dispence w/ the brackets like this...
match choice.trim().to_lowercase().as_str() {
      "yes" | "y" => {
          println!("Going back to the main menu:");
          break;
      }
      _ => {
          continue;
      }
  }

...becomes...

match choice.trim().to_lowercase().as_str() {
    "yes" | "y" => {
        println!("Going back to the main menu:");
        break;
    }
    _ => continue,
}

Thank you very much for your valuable time and review, I have given the edited code, it's something more, I would be glad if you reviewed it too!

use rand::RngExt; //Written for rand v0.10.2
use std::fs;
use std::io;
use std::path::Path;
const SAVE_NAMES: &str = "names.txt";

fn save_names(list: &[String]) {
    let content = list.join("\n");

    match fs::write(SAVE_NAMES, content) {
        Ok(_) => {}
        Err(e) => println!("Warning: Could not save names: {}", e),
    }
}
fn load_names() -> Vec<String> {
    let mut names: Vec<String> = Vec::new();

    // If the file doesn't exist yet, return an empty Vec
    if !Path::new(SAVE_NAMES).exists() {
        return names;
    }

    let content = match fs::read_to_string(SAVE_NAMES) {
        Ok(c) => c,
        Err(e) => {
            println!("Warning: Could not load names: {}", e);
            return names;
        }
    };

    for line in content.lines() {
        let line = line.trim();
        if line.is_empty() {
            continue;
        }
        names.push(line.to_string());
    }

    names
}

fn show_names(list: &[String]) {
    if list.is_empty() {
        println!("No names to show! Add some names first.");
        return;
    }
    println!("All names:");
    for (i, name) in list.iter().enumerate() {
        println!("{}. {}", i + 1, name);
    }
}

fn capitalise_first(s: &str) -> String {
    let mut chars = s.chars();
    match chars.next() {
        None => String::new(),
        Some(c) => c.to_uppercase().to_string() + &chars.as_str(),
    }
}

fn add_names(list: &mut Vec<String>) {
    loop {
        loop {
            println!("Enter the name to add or 0 to exit:");
            let mut name = String::new();
            io::stdin().read_line(&mut name).expect("Failed.");
            if name.trim() == "0" {
                break;
            }
            if name.trim().is_empty() {
                continue;
            }
            let name_f = capitalise_first(name.trim());
            if list.contains(&name_f) {
                println!("Name already available.");
                continue;
            }
            list.push(name_f);
        }
        println!("New name list:");
        show_names(list);
        save_names(list);
        println!("Do you want to go back to the main menu?Write 'Yes' to go back.");
        let mut choice = String::new();
        io::stdin().read_line(&mut choice).expect("Failed");
        match choice.trim().to_lowercase().as_str() {
            "yes" | "y" => {
                println!("Going back to the main menu:");
                break;
            }
            _ => continue,
        }
    }
}

fn lottery(list: &[String]) {
    println!("Do lotteries here!(Repeat possible, wheel style)");
    show_names(list);
    if list.is_empty() {
        return;
    }
    loop {
        println!("How many names to pick? Enter the number:");
        let mut count_input = String::new();
        io::stdin().read_line(&mut count_input).expect("Failed");
        let count: usize = match count_input.trim().parse() {
            Ok(n) if n >= 1 && n <= list.len() => n,
            Ok(_) => {
                println!("Please enter a number between 1 and {}.", list.len());
                continue;
            }
            Err(_) => {
                println!("Error: Please enter a valid number.");
                continue;
            }
        };

        let mut picked: Vec<&str> = Vec::new();
        let mut rng = rand::rng();
        for _ in 0..count {
            picked.push(&list[rng.random_range(0..list.len())]);
        }
        for (i, name) in picked.iter().enumerate() {
            println!("{}. {}", i + 1, name);
        }
        println!("Do you want to go back to the main menu? Type 'yes' to go back:");
        let mut choice = String::new();
        io::stdin().read_line(&mut choice).expect("Failed");
        match choice.trim().to_lowercase().as_str() {
            "yes" | "y" => {
                println!("Going back to the main menu.");
                break;
            }
            _ => continue,
        }
    }
}
fn del_names(list: &mut Vec<String>) {
    if list.is_empty() {
        println!("No names to delete.");
        return;
    }
    show_names(list);
    let mut indices_to_delete: Vec<usize> = Vec::new();
    loop {
        println!("Enter the name number to delete or q to exit:");
        let mut del_in = String::new();
        io::stdin().read_line(&mut del_in).expect("Failed");
        if del_in.trim() == "q" {
            break;
        }
        let delete: usize = match del_in.trim().parse() {
            Ok(n) if n > 0 && n <= list.len() => n,
            Ok(_) | Err(_) => {
                println!(
                    "Invalid input,enter a valid number between 1 and {}",
                    list.len()
                );
                continue;
            }
        };
        let idx = delete - 1;
        if indices_to_delete.contains(&idx) {
            println!("Line {} is already marked for deletion.", idx + 1);
            continue;
        }
        indices_to_delete.push(idx);
        println!("Marked for deletion:");
        let pending: Vec<String> = indices_to_delete.iter()
    .map(|i| (i + 1).to_string())
    .collect();
println!("Marked for deletion: {}", pending.join(", "));
    }
    //Remove in reverse order so that the indices stay valid
    indices_to_delete.sort_unstable_by(|a, b| b.cmp(a));
    println!("Deleting.....");
    for idx in indices_to_delete {
        list.remove(idx);
    }
    println!("Updated list:");
    show_names(list);
    save_names(list);
}
fn main() {
    let mut list: Vec<String> = load_names();
    loop {
        println!("{}", "=".repeat(42));
        println!("          LOTTERY APP ");
        println!("{}", "=".repeat(42));
        println!("Here you can do lotteries!\n");
        println!("Choose an option:");
        println!("1. Add name");
        println!("2. See all names");
        println!("3. Do lotteries");
        println!("4. Delete names");
        println!("5. Exit");
        let mut choice_input = String::new();
        io::stdin().read_line(&mut choice_input).expect("Failed");
        let choice: usize = match choice_input.trim().parse() {
            Ok(n) => n,
            Err(_) => {
                println!("Error: Please enter a valid number.");
                continue;
            }
        };
        match choice {
            1 => add_names(&mut list),
            2 => show_names(&list),
            3 => lottery(&list),
            4 => del_names(&mut list),
            5 => {
                println!("Thank you for using the lottery app, goodbye!");
                break;
            }
            _ => {
                println!("Please choose a valid option between 1 to 5.");
                continue;
            }
        }
    }
}

good effort but still needs attention. here are some areas you may want to spend time on...

  • the add_names function could probably be made more solid using something like inflections crate.
  • aspects like personal titles (e.g. mr., mrs., ms. etc...) should probably be removed. sticking to names being formed by first, middle, and last parts (or the equivalent in your part of the world) should make it clearer how to "standardize" them (first name first, or last, etc...).
  • if you insist on using a terminal medium, may be using a clap crate would be helpful.
  • if they're kept, the show_names function should be amended accordingly.
  • what happens when the list of names in names.txt get bigger?

well done + good luck!

About the points, I'm still a beginner, and it's my first, so I'm not still familiar with the crates you mentioned. And in my region we have similar name systems, however I don't think my project needs that yet. And it's just a small project, so the names.txt file won't get much bigger, besides it's more like an exercise for me. And thank you very much for your suggestions!

if this is your first Rust project then well done!

few points you may be interested in pursing:

  • when adding new names, remove personal titles and only compare the names, to check if they are unique.
  • another alternative would be to ask for individual name-parts and assemble them programmatically.
  • always ensure users, or at least notify them, adding names should forllow certain order: first, middle, last; or first, last for example if you decide to continue w/ prompting for a full name.
  • usually, but not always, when you're at a certain point in developing your software, you would publish (w/ cargo publish) the deliverable to crates.io. it's there where you can add libraries, your own code may use. instructions on how to add libraries to your software's Cargo.toml should be there.
  • acquaint yourself w/ cargo and its commands. you will not regret it.

good luck!

Binary applications usually aren't published as crates, and code that is purely a learning exercise for yourself also usually shouldn't be published.

The specific subject matter of conducting a raffle or lottery is also legally encumbered in most jurisdictions, with specific legal requirements about the quality and auditing of your random number generation. I suggest looking up those laws if this is anything more than a learning exercise.

Thank you very much, however it's just a basic learning exercise for me, also I don't intend to make it in any serious projects, besides there's another problem, I should look up for which country's law? USA or my own? Besides it's completely irrelevant for a toy project like this.