millionaire 0.3.0

Game written in Rust. The goal is to get 1 million dollars by trading stonks.
Documentation
use std::collections::HashMap;
use std::fmt::Display;
use std::hash::Hash;
use std::io::{self, Write};
use millionaire::{self, Player, Stock};

fn number_input(prompt: &str) -> Result<usize, io::Error> {
    loop {
        print!("{}", prompt); io::stdout().flush()?;
        let mut choice = String::new();
        io::stdin().read_line(&mut choice)?;
        let choice = choice.trim();

        let choice: usize = match choice.parse() {
            Ok(i) => i,
            Err(_) => {
                println!("`{}` was not a number!\n", choice);
                continue;
            }
        };

        return Ok(choice);
    }
}

fn menu<T: Hash + Display>(options: &[T]) -> Result<&T, io::Error> {
    loop {
        let mut map = HashMap::new();

        for (idx, t) in options.iter().enumerate() {
            let idx = idx + 1;
            map.insert(idx, t);
            println!("{}. {}", idx, t);
        }
        let choice = number_input("Please choose an option: ").expect("IO Error");
        
        return match map.get(&choice) {
            Some(t) => Ok(*t),
            None => {
                println!("Invalid choice, `{}`!\n", choice);
                continue;
            }
        }
    }
}

fn net_worth_breakdown(player: &Player, stocks: &[Stock]) {
    println!("---");
    println!("Balance: {}", player.balance());
    for s in stocks {
        let value = s.value();
        let stock_balance = player.stock_balance(s);
        println!("Stock: '{}', Balance: {}, Value: {}, Worth: {}", s.name(), stock_balance,
                 value, stock_balance * value);
    }
    println!("\nNet worth: {}", player.net_worth(stocks));
    println!("---");
}

fn double_check(prompt: &str, default: bool) -> Result<bool, io::Error> {
    print!("{} {} ", prompt, if default { "(Y/n)" } else { "(y/N)" });
    io::stdout().flush()?;

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

    if default {
        Ok(!choice.starts_with("n"))
    } else {
        Ok(choice.starts_with("y"))
    }
}

fn main() {
    let mut goal = 1_000_000;
    let mut income = 1000;
    let mut initial_balance = 1000;
    let mut new_stock_cost = 15000;

    loop {
        let options = ["Play game!", "Quit"];
        match *menu(&options).expect("IO error") {
            "Play game!" => {
                let mut run_game = true;
                let initial_income = income;
                let mut player = Player::new(initial_balance, income);
                
                let mut stocks = Vec::new();

                for _ in 0..3 {
                    let name = millionaire::generate_name();
                    let stock = millionaire::generate_stock(stocks.len() as i64, 10, 100, 
                                                            10, 100, name);
                    stocks.push(stock);
                }
                let options = ["Buy stocks", "Sell stocks", "Increase income",
                               "Add a new stock", "Print net worth breakdown", 
                               "End turn", "Quit game"];

                while run_game {
                    for s in stocks.iter_mut() {
                        if s.value() <= 0 {
                            println!("Stock '{}' went bankrupt!", s.name());
                            s.reset();
                            player.reset_stock(s);
                        }
                    }

                    let mut breakdown_printed = false;
                    if player.net_worth(&stocks) > goal {
                        net_worth_breakdown(&player, &stocks);
                        println!("You win!");
                        break;
                    }

                    
                    loop {
                        println!();
                        if !breakdown_printed {
                            net_worth_breakdown(&player, &stocks);
                            breakdown_printed = true;
                        } else {
                            println!("Balance: {}\n", player.balance());
                        }

                        let choice = *menu(&options).expect("IO error");
                        println!();
                    
                        match choice {
                            "Buy stocks" => {
                                let stock = menu(&stocks).expect("IO error");
                                let prompt = format!(
                                        "How much stock would you like to buy? (Max: {}) ",
                                        player.balance() / stock.value());
                                let amount = number_input(&prompt)
                                    .expect("IO Error");
                                if let Err(()) = player.buy_stock(stock, amount as i64) {
                                    println!("You could not afford that much stock.");
                                }
                            }
                            "Sell stocks" => {
                                let stock = menu(&stocks).expect("IO error");
                                let prompt = format!(
                                        "How much stock would you like to sell? (Max: {}) ",
                                        player.stock_balance(stock));
                                let amount = number_input(&prompt)
                                    .expect("IO Error");
                                if let Err(()) = player.sell_stock(stock, amount as i64) {
                                    println!("You do not have enough stock.");
                                }
                            }
                            "Increase income" => {
                                println!("An income increase costs {}.", initial_income * 10);
                                if double_check(
                                    "Are you sure you want to increase your income?", true
                                ).expect("IO Error") {
                                    if let Err(()) = player.increase_income() {
                                        println!("You couldn't afford an income increase.");
                                    }
                                }
                            }
                            "Add a new stock" => {
                                println!("Adding a new stock costs {}", new_stock_cost);
                                if double_check(
                                    "Are you sure you want to unlock a new stock?", true
                                ).expect("IO error") {
                                    if let Err(()) = player.withdraw(new_stock_cost) {
                                        println!("You couldn't afford a new stock.");
                                    } else {
                                        let name = millionaire::generate_name();
                                        let stock = millionaire::generate_stock(
                                            stocks.len() as i64, 10, 100, 10, 100, name);
                                        stocks.push(stock);
                                    }
                                }
                            }
                            "Print net worth breakdown" => { 
                                net_worth_breakdown(&player, &stocks);
                            }
                            "End turn" => { 
                                player.collect_income();
                                break; 
                            }
                            "Quit game" => {
                                if double_check("Are you sure you want to end the game?", 
                                                false).expect("IO Error") {
                                    run_game = false;
                                    break;
                                }
                            }
                            _ => { panic!("unreachable case in game loop"); }
                        }
                    }

                    for s in stocks.iter_mut() {
                        s.vary();
                    }
                }
                println!();
            }
            "Quit" => {
                println!("Goodbye ;(");
                break;
            }
            _ => { panic!("unreachable case in the main loop"); }
        }
    }
}