First: this usecase sounds like you need a database. That will manage things like concurrent connections, multi-part transactions, saving, and restoring. That being said, the simple read/replace functionality you described is absolutely possible using Rust HashMap
s.
The main issue with what you're showing is indirection. It is a better idea to use one map, where the key is (manufacture, id)
, as opposed to two maps, one that maps manufacturers to a list of ids, and then maps for each of those ids to vehicle. When you use just one map with the double key, it takes just one lookup to access and store values, instead of two.
The downside of doing it this way is that you cannot create a manufacturer that does not have cars. It also becomes harder (not impossible, just harder) to list all manufacturers, or list all the cars of a single manufacturer.
A big difference between Rust and PHP is data control and ownership. In Rust, you must prove to the compiler that everything you do it valid. In PHP, it's just assumed you know what you're doing.
Here is a functional example of what could work. However, this code is not thoroughly tested, and does not handle errors well. Specifically, Bids::get_entry
will panic
if you try to access a car that does not exist. Bids::handle_bid
, and Bids::print_current_bid
also will do this. The unwraps could be removed and handled intelligently. For example, Bids::entry
would be better to return Option<&BidInfo>
. The reason I did not do this is because the example I showed was already pretty long, and I wanted to keep the code as minimal as possible.
use std::collections::HashMap;
struct BidInfo {
/// The current bid
current_bid: f64,
/// The time of the last bid, as a unix epoch
last_bid_time: i64,
}
impl BidInfo {
/// Create a new vehicle
fn new() -> BidInfo {
BidInfo {
current_bid: 0.0,
last_bid_time: 0,
}
}
}
// To use CarInfo as the key of a HashMap, Rust needs to know how to:
// * hash it
// * test if it's equal to something else
//
// Using derive, we tell Rust to automatically generate the code to do this.
#[derive(PartialEq, Eq, Hash, Clone)]
struct CarInfo {
manufacturer: String,
model_id: u8,
}
impl CarInfo {
fn new(manufacturer: &str, model_id: u8) -> CarInfo {
CarInfo {
manufacturer: manufacturer.to_string(),
model_id: model_id,
}
}
}
// This type is the key
// vvvvvvv
struct Bids (HashMap<CarInfo, BidInfo>);
// This type is the value ^^^^^^^
impl Bids {
/// Create a new, empty, bids object
fn new() -> Bids {
Bids(HashMap::new())
}
/// Add a new car to the table
/// Assume that it's current bid info is empty
fn add_entry(&mut self, car: CarInfo) {
self.0.insert(car, BidInfo::new());
}
/// Get the current bid info for a given car
/// Note, we cannot modify this entry, we can just read it
/// If the car does not exist, this function throws an error
fn get_entry(&self, car: &CarInfo) -> &BidInfo {
self.0.get(car).unwrap()
}
/// Handle a new bid
fn handle_bid(&mut self, car: &CarInfo, bid: f64) {
// We use get_mut because we want to edit what we get back
// This gives us an Option<BidInfo>. If the car was in the map, it will
// contain the bid info, but if the car does not exist, it will be a
// special value, None
//
// This unwrap function assumes that it worked. If we try to unwrap a
// value that is None, we will get a runtime error
let mut entry = self.0.get_mut(car).unwrap();
entry.current_bid = bid;
entry.last_bid_time = entry.last_bid_time + 1; // This doesn't actually set timestamps, but you get the idea
}
/// Helper function that gives us information about the current bid of a car
fn print_current_bid(&self, car: &CarInfo) {
println!(
"Current bid of {} model {} is {}",
car.manufacturer,
car.model_id,
self.get_entry(car).current_bid
);
}
}
fn main() {
// For this example, we'll use two different manufacturers, each with 3
// cars
// Create empty map
let mut bids = Bids::new();
// Create a new car with manufacturer Volvo and model_id 0
bids.add_entry(CarInfo::new("Volvo", 0));
bids.add_entry(CarInfo::new("Volvo", 1));
bids.add_entry(CarInfo::new("Volvo", 32));
// We can also create entries this way
let bmw_car_0 = CarInfo::new("BMW", 0);
let bmw_car_1 = CarInfo::new("BMW", 1);
let bmw_car_3 = CarInfo::new("BMW", 3);
// We use clone here to create a copy of the car data, so we can keep the one we created
bids.add_entry(bmw_car_0.clone());
bids.add_entry(bmw_car_1.clone());
bids.add_entry(bmw_car_3.clone());
// Print out some state to verify everything is okay
bids.print_current_bid(&CarInfo::new("Volvo", 1));
// This method takes a &CarInfo instead of CarInfo. This means that instead
// of taking the object, it just borrows it for a bit. Therefore, we don't
// have to use clone this time
bids.print_current_bid(&bmw_car_1);
// This would cause an error, because the entry doesn't exist
//bids.print_current_bid(CarInfo::new("BMW", 33));
// Handle a few bids
bids.handle_bid(&CarInfo::new("Volvo", 1), 100.0);
bids.handle_bid(&bmw_car_1, 322.0);
// Print out the same info
bids.print_current_bid(&CarInfo::new("Volvo", 1));
bids.print_current_bid(&bmw_car_1);
}