I'm learning Rust and a few questions did arise during translation of my C++ code to Rust. There are comments in Rust code I'd like to be answered. Is there an idiomatic way to solve this task? The task was in simulating a random process - there are two chairs, which have different processing capacity and there is a flow of customers, who visit the chairs sequentially.
Summary: Shoe shine shop has two chairs, one for brushing (1) and another for polishing (2). Customers arrive according to PP with rate \$\lambda\$, and enter only if first chair is empty. Shoe-shiners takes \$\exp(\mu_1)\$ time for brushing and \$\exp(\mu_2)\$ time for polishing.
Code in C++:
#include <map> #include <string> #include <random> #include <iostream> #include <numeric> #include <algorithm> #include <queue> int main(int argc, char *argv[]) { if (argc < 5) { std::cerr << "not enough arguments!\nlambda, m1, m2, max_time"; return -1; } using distribution_t = std::exponential_distribution<double>; std::string event_names[3] = {"ARRIVED", "FIRST_FINISHED", "SECOND_FINISHED"}; std::string state_names[7] = {"EMPTY", "FIRST", "SECOND", "WAITING", "BOTH", "DROP", "INVALID"}; enum event_t { ARRIVED = 0, FIRST_FINISHED, SECOND_FINISHED }; enum state_t { EMPTY = 0, FIRST, SECOND, WAITING, BOTH, DROP, INVALID }; std::size_t state_to_clients[DROP] = {0, 1, 1, 2, 2}; // clang-format off // EMPTY FIRST SECOND WAITING BOTH state_t event_to_state[3][5] = { /* ARRIVED */ {FIRST, DROP, BOTH, DROP, DROP}, /* FIRST_FINISHED */ {INVALID, SECOND, INVALID, INVALID, WAITING}, /* SECOND_FINISHED */ {INVALID, INVALID, EMPTY, SECOND, FIRST}, }; // clang-format on double lambda = atof(argv[1]); double m1 = atof(argv[2]); double m2 = atof(argv[3]); double time_max = atof(argv[4]); std::mt19937_64 generator(std::random_device{}()); struct stats_t { std::size_t state_counts[DROP]{}; // max feasible event - BOTH std::size_t state_counts_with_drop[DROP]{}; double time_in_state[DROP]{}; double time_in_client[3]{}; // roflanEbalo double served_time = 0.0; std::size_t served_clients = 0; std::size_t arrived_clients = 0; std::size_t dropped_clients = 0; } stats; double times[3]{}; distribution_t dists[3] = {distribution_t(lambda), distribution_t(m1), distribution_t(m2)}; // mean = 1/param std::map<double, event_t> timeline; auto inserter = [&timeline, &generator](event_t event, double &t, distribution_t &dist) { double dt; do { dt = dist(generator); } while (!timeline.try_emplace(t + dt, event).second); t += dt; }; for (std::size_t i = 0; i < 3; ++i) while (times[event_t(i)] < time_max) inserter(event_t(i), times[i], dists[i]); double prev = 0; state_t state = EMPTY; std::queue<double> arriving_times; for (auto [time, event] : timeline) { if (argc > 5) { std::cout << "[PROCESSING]: " << time << " " << event_names[event] << std::endl; std::cout << "[INFO]: " << state_names[state] << std::endl; } if (event == ARRIVED) ++stats.arrived_clients; state_t new_state = event_to_state[event][state]; switch (new_state) { case INVALID: break; case DROP: ++stats.state_counts_with_drop[state]; ++stats.dropped_clients; break; default: if (event == ARRIVED) arriving_times.push(time); else if (event == SECOND_FINISHED) { stats.served_time += time - arriving_times.front(); arriving_times.pop(); ++stats.served_clients; } stats.time_in_state[state] += time - prev; stats.time_in_client[state_to_clients[state]] += time - prev; prev = time; state = new_state; ++stats.state_counts[state]; break; } } std::transform(std::begin(stats.state_counts), std::end(stats.state_counts), std::begin(stats.state_counts_with_drop), std::begin(stats.state_counts_with_drop), std::plus<std::size_t>()); auto report = [&state_names](std::string_view title, auto counts) { std::cout << title << std::endl; auto events = std::accumulate(counts, counts + DROP, 0.0); for (std::size_t i = 0; i < DROP; ++i) std::cout << state_names[i] << ": " << counts[i] / double(events) << std::endl; std::cout << std::endl; }; report("time in states: ", stats.time_in_state); report("entries in states: ", stats.state_counts); report("entries in states with dropouts: ", stats.state_counts_with_drop); std::cout << "dropout: " << stats.dropped_clients / double(stats.arrived_clients) << std::endl; std::cout << "average serving time: " << stats.served_time / double(stats.served_clients) << std::endl; std::cout << "average number of clients: " << (stats.time_in_client[1] + 2 * stats.time_in_client[2]) / std::accumulate(std::begin(stats.time_in_client), std::end(stats.time_in_client), 0.0) << std::endl; // arr=(10 10 10); for i in {0..2}; do for param in {1..100}; do // darr=("${arr[@]}"); darr[i]=${param}; echo "${darr[@]}" >> ../out.txt && // ./lab2.exe ${darr[@]} 1000000 >> ../out.txt; done; done } Code in Rust:
use std::collections::BTreeMap; use std::collections::VecDeque; use std::env; extern crate rand; use rand::distributions::*; extern crate ordered_float; pub use ordered_float::*; // variant is never constructed: `FirstFinished`, why do I get this message? I can see this variant printed when running the program #[derive(Copy, Clone, Debug, PartialEq)] enum Events { Arrived = 0, FirstFinished, SecondFinished, } #[derive(Copy, Clone, Debug, PartialEq)] enum States { Empty = 0, First, Second, Waiting, Both, Dropping, Invalid, } #[rustfmt::skip] #[derive(Debug, Default)] struct Stats { state_counts: [u32; States::Dropping as usize], state_counts_with_drop: [u32; States::Dropping as usize], time_in_state: [f64; States::Dropping as usize], time_in_client: [f64; 3], served_time: f64, served_clients: u32, arrived_clients: u32, dropped_clients: u32, } // 1 template function for this? Or any other way to cast integer to enum? Or I should use libraries for this? impl From<usize> for States { fn from(s: usize) -> States { let tmp: u8 = s as u8; unsafe { std::mem::transmute(tmp) } } } impl From<usize> for Events { fn from(s: usize) -> Events { let tmp: u8 = s as u8; unsafe { std::mem::transmute(tmp) } } } //what do I need lifetime 'a for? Is there supertrait that specifies multiple traits? ("Number", "container", idk) //Or can I just say that allowed types are f64 and i32? fn report<'a, T>(title: &str, counts: &'a [T; States::Dropping as usize]) where T: std::iter::Sum<&'a T> + std::ops::Div + Copy + Into<f64> + std::fmt::Display, { println!("{}", title); let events: T = counts.iter().sum(); for i in 0..(States::Dropping as usize) { println!( "{:?}: {}", Into::<States>::into(i), Into::<f64>::into(counts[i]) / Into::<f64>::into(events) // How to call Into properly? this looks bad ); } println!(); } fn main() { let state_to_clients: [usize; States::Dropping as usize] = [0, 1, 1, 2, 2]; #[rustfmt::skip] let event_to_state: [[States; 5]; 3] = [ // EMPTY FIRST SECOND WAITING BOTH /* Arrived */ [States::First, States::Dropping, States::Both, States::Dropping, States::Dropping], /* First_Finished */ [States::Invalid, States::Second, States::Invalid, States::Invalid, States::Waiting], /* Second_Finished */ [States::Invalid, States::Invalid, States::Empty, States::Second, States::First], ]; let args: Vec<String> = env::args().collect(); if args.len() < 5 { panic!("Not enough arguments!"); } let (lambda, m1, m2, time_max) = ( args[1].parse::<f64>().unwrap(), args[2].parse::<f64>().unwrap(), args[3].parse::<f64>().unwrap(), args[4].parse::<f64>().unwrap(), ); let mut rng = rand::thread_rng(); let mut stats = Stats::default(); let mut times: [f64; 3] = Default::default(); let mut dists: [Exp; 3] = [Exp::new(lambda), Exp::new(m1), Exp::new(m2)]; // I don't like OrderedFloat because it's a wrapper. Is there a way to implement Ord for floats and keep nice syntax? // Maybe it's the problem of algorithm. Any proposals? let mut timeline: BTreeMap<OrderedFloat<f64>, Events> = BTreeMap::new(); let mut inserter = |event: &Events, t: &mut f64, distribution: &mut Exp| { let mut dt; //Is it ok to emulate do while loops like this? while { dt = OrderedFloat(distribution.sample(&mut rng)); let key = OrderedFloat(*t + Into::<f64>::into(dt)); match timeline.get(&key) { Some(_) => true, None => { timeline.insert(key, *event); false } } } {} *t += Into::<f64>::into(dt); }; for i in 0..3 { while times[i] < time_max { inserter(&i.into(), &mut times[i], &mut dists[i]); } } let mut prev = 0f64; let mut state = States::Empty; let mut arriving_times = VecDeque::<f64>::new(); for (time, event) in timeline { if args.len() > 5 { println!("[PROCESSING]: {} {:?}", time, event); println!("[INFO]: {:?}", state); } if event == Events::Arrived { stats.arrived_clients += 1; } let new_state = event_to_state[event as usize][state as usize]; match new_state { States::Dropping => { stats.state_counts_with_drop[state as usize] += 1; stats.dropped_clients += 1; } States::Invalid => (), _ => { if event == Events::Arrived { arriving_times.push_back(Into::<f64>::into(time)); } else if event == Events::SecondFinished { stats.served_time += Into::<f64>::into(time) - arriving_times.front().unwrap(); arriving_times.pop_front(); stats.served_clients += 1; } stats.time_in_state[state as usize] += Into::<f64>::into(time) - prev; stats.time_in_client[state_to_clients[state as usize] as usize] += Into::<f64>::into(time) - prev; prev = Into::<f64>::into(time); state = new_state; stats.state_counts[state as usize] += 1; } }; } for (i, element) in stats.state_counts_with_drop.iter_mut().enumerate() { *element += stats.state_counts[i]; } report("time in states: ", &stats.time_in_state); report("entries in states: ", &stats.state_counts); report( "entries in states with dropouts: ", &stats.state_counts_with_drop, ); println!( "dropout: {}\naverage serving time: {}\naverage number of clients: {}", (stats.dropped_clients as f64) / (stats.arrived_clients as f64), stats.served_time / (stats.served_clients as f64), (stats.time_in_client[1] + 2.0f64 * stats.time_in_client[2]) / stats.time_in_client.iter().sum::<f64>() ); }