Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to index an array by enums in Rust?

Tags:

I want to represent a table of data in the memory like the following:

     | USD | EUR | -----+-----+-----+ John | 100 | 50  | -----+-----+-----+ Tom  | 300 | 200 | -----+-----+-----+ Nick | 200 | 0   | -----+-----+-----+ 

There is a known set of people, each of them owns some currency.

And I have the following enums:

enum Person {     John,     Tom,     Nick }  enum Currency {     USD,     EUR } 

I'd like to encode this data as 2D array, and it would be cool to be able to index array elements not by usize but by enum. E.g.:

data[Person::John][Currency::USD] = 100; 

Is it possible to do with arrays and enums in Rust? Or is there any other data structure that would serve for this?

I am aware of HashMap, but it's not exactly what I want because:

  • HashMap works on the heap (what makes it much slower than regular stack allocated array)

  • HashMap gives me no guarantee that item exist. E.g. every time I want to get something I have to unwrap it and handle None case, what is not very handy in comparison with usage of normal array.

This is different from How do I match enum values with an integer? because I am not interested in converting enum to usize; I just want a handy way to access array/map items by enum.

like image 705
Sergey Potapov Avatar asked Jul 04 '17 08:07

Sergey Potapov


2 Answers

ljedrz provided a good solution. Another way to approach the problem is to use existing crate enum-map.

Add the following to your Cargo.toml:

[dependencies] enum-map = "*" enum-map-derive = "*" 

Then, in src/main.rs:

extern crate enum_map; #[macro_use] extern crate enum_map_derive;  #[derive(Debug, EnumMap)] enum Person { John, Tom, Nick }  #[derive(Debug, EnumMap)] enum Currency { USD, EUR }  use enum_map::EnumMap; use Person::*; use Currency::*;  fn main() {     // Create 2D EnumMap populated with f64::default(), which is 0.0     let mut table : EnumMap<Person, EnumMap<Currency, f64>> = EnumMap::default();      table[John][EUR] = 15.25;      println!("table = {:?}", table);     println!("table[John][EUR] = {:?}", table[John][EUR]); } 

The output:

table = EnumMap { array: [EnumMap { array: [0, 15.25] }, EnumMap { array: [0, 0] }, EnumMap { array: [0, 0] }] } table[John][EUR] = 15.25 
like image 151
Sergey Potapov Avatar answered Oct 03 '22 20:10

Sergey Potapov


If you need this to be implemented using arrays, this isn't as straightforward as it might seem.

In order to be able to contain both of these pieces of information in an array (to be able to index by them), you first need to combine them in a single type, e.g. in a struct:

struct Money([(Currency, usize); 2]);  struct PersonFinances {     person: Person,     money: Money } 

Then, if you want to be able to index the table, you will need to wrap it in your own type so that you can implement the Index trait for it:

use std::ops::Index;  struct Table([PersonFinances; 3]);  impl Index<(Person, Currency)> for Table {     type Output = usize;      fn index(&self, idx: (Person, Currency)) -> &Self::Output {         &self         .0         .iter()         .find(|&pf| pf.person == idx.0) // find the given Person         .expect("given Person not found!")         .money         .0         .iter()         .find(|&m| m.0 == idx.1)  // find the given Currency         .expect("given Currency not found!")         .1     } } 

Then you can index the Table by a Person, Currency pair:

table[(Tom, EUR)] 

Rust playground link to the whole code

like image 23
ljedrz Avatar answered Oct 03 '22 20:10

ljedrz