I need help with macros and enums

Hello there,

I am currently working on a geotiff parser where I want to parse a bunch of geology data into enums.

So I try to make this as quick as I can. Basically a geokey_directory contains data.

This data can be one of these types: Short, Vec<Short>, Vec<Double>, String.

Okay so long quite easy. Now each directory contains several keys and values which hold some of the data. So, e.g. the directory might look like this (simplified):

Key data Note
3075 17 defines the ProjCoordTransGeoKey
1026 `"unnamed "`
2057 3396190.0 stands for GeogSemiMajorAxisGeoKey

Okay cool, so what I want to do is, I want to store the data in an enum so that I later know that ProjCoordTransGeoKey stands for 3075 and I don't have to remember what number that was. So I need to instatiate an enum by its value. I did this with the help of the matrix Rust group here sucessfully.

So now have a an enum for the keys, an enum for the values and save this as key value pairs in a hashmap.

Okay round two.

Now, when you look at the table above, the third entry indeed refers to a double value, in this case it refers to the mean radius of Mars. However the first entry does not refer to the number 17, rather it refers to a type (like an enum) for which number 17 means e.g. CT_Equirectangular.

So some of the shorts actually refer to codes in the geotiff spec (basically eleven other enums).

What I would like to do now is two things:

  1. Instead of saving things in a hashmap I would like the enum itself to save the values and
  2. I would like to parse the shorts which belong to other enums, to these enums.

I don't know how clear this is but on my branch I already tried to refactor my code and define all enums here however I have no idea on how to change my macro so I can use it here to parse and save the values I got from the geotiff.
I also have a test which shows how I would like to access the data later.

As a minimal example this is what I like to do:

enum_try_from! {
pub enum GeoKey {
    Bla1(GeoKeyData) = 1024,
    Bla2(SomeGeoEnum) = 1025,
    // ...
}
}
// contains primitive types
pub enum GeoKeyData {
    Short(u16),
    // ...
}

// One of the 11 enums:
pub enum SomeGeoEnum{
    Elipsoidblablalba = 32421,
    // ...
}

// Usage:
let new_entry = GeoKey::create_new(1025, 32421)

// Test how I would like to access:
match new_entry {
    GeoKey::Bla2(value) => assert_eq!(value, SomeGeoEnum::Elipsoidblablalba),
    // ... 
}

// Usage for primitive values:
let other_entry = GeoKey::create_new(1024, GeoKeyData::Short(123));

match other_entry {
    GeoKey::Bla1(value) => assert_eq!(value, GeoKeyData::Short(123)),
    // ... 
}

I am very grateful if anyone can help me as I am pretty frustrated by now. What I have in my repo does not work and I don't know where to go. Please go easy on me as I just started Rust a few months ago and am a newb. Thats why my code is so shitty :slight_smile:

Hello @Rudio :wave:, and welcome :slightly_smiling_face:

On nightly I've managed to have the following working:

#![feature(min_const_generics)]
use ::core::convert::{TryFrom, TryInto};

derive_create_new! {
    #[derive(Debug, PartialEq, Eq)]
    pub
    enum GeoKey {
        Bla1(GeoKeyData) = 1024,
        Bla2(SomeGeoEnum) = 1025,
        // ...
    }
}

#[derive(Debug, PartialEq, Eq)]
pub
enum GeoKeyData {
    Short(u16),
    // ...
}

// One of the 11 enums:
derive_TryFrom! {
    #[derive(Debug, PartialEq, Eq)]
    pub
    enum SomeGeoEnum {
        Elipsoidblablalba = 32421,
        // ...
    }
}

// Usage:
fn main ()
{
    let new_entry = GeoKey::create_new::<_, 1025>(32421).unwrap();
    
    // Test how I would like to access:
    match new_entry {
        | GeoKey::Bla2(value) => assert_eq!(value, SomeGeoEnum::Elipsoidblablalba),
        | _ => { /* … */ },
    }
    
    // Usage for primitive values:
    let other_entry = GeoKey::create_new::<_, 1024>(GeoKeyData::Short(123)).unwrap();
    
    match other_entry {
        | GeoKey::Bla1(value) => assert_eq!(value, GeoKeyData::Short(123)),
        | _ => { /* … */ },
    }
}

The key idea, and also the complication for your example, is that depending on the discriminant given, such as 1025, one payload needs to be targetted rather than another, which means the type of the second parameter depends on the value of the first. This is not possible in Rust (unless you go full dynamic typing with Any), unless that first parameter is not a real parameter, but a generic parameter.

Hence the usage of nightly: to use a number as a generic parameter, the simplest solution is to use the min_const_generic unstable-thus-nightly-but-soon-to-be-stabilized feature.

  • That being said, since you can create dummy types such as struct _42; to represent, at compile-time, the number 42, and since, in your use-case, the usable numbers are know upfront, it is possible, for a macro, to forge all such fake-type-as-compile-time-number, and then, for the caller, to use those instead of const generics by prepending that disambiguating underscore. This allows us to no longer need nightly:

    // Usage
    let new_entry = GeoKey::create_new(_1025, 32421).unwrap();
    
    // Test how I would like to access:
    match new_entry {
        | GeoKey::Bla2(value) => assert_eq!(value, SomeGeoEnum::Elipsoidblablalba),
        | _ => { /* … */ },
    }
    
    // Usage for primitive values:
    let other_entry = GeoKey::create_new(_1024, GeoKeyData::Short(123)).unwrap();
    
    match other_entry {
        | GeoKey::Bla1(value) => assert_eq!(value, GeoKeyData::Short(123)),
        | _ => { /* … */ },
    }
    
1 Like

OH my god, thank you so much for your thourough and helpful answer, jesus christ. I will take some time now to look into this and try it out.

I cannot stress enough how thankful I am! Again and again this community astonishes me by the friendliness and the time people spent on helping strangers on the internet. Thank you!

2 Likes

Hey @Yandros, so I implemented the macros and have two more questions:

  1. So it seems to work if I use it in the same file, however if I want to use the create_from in a different file it cannot find my test value _1026. I already made create_new public and added a macro_use to the file where the macros are defined. But it still does not work.

  2. When I can use values like 1026 as _1026 from outside, how would I do this with variables? E.g. in reality I get my short from an vec, so e.g. I probably cannot make GeoKey::create_new(geokey_dir[idx], value) into GeoKey::create_new(_geokey_dir[idx], value) because that becomes a new unknown variable name.

@Rudio indeed, the _1026 "magic numbers" are "constants" defined in the module where the macro is called / the enum is defined, so you would need to use path::to::that::module::*; in order to have them in scope.

This usage changes everything: we are no longer talking of compile-time constants, but of a runtime discriminant.

Which means the only approach to feature such a constructor will be to use the dynamic typing approach :weary:.

I will show how to do it later, I don't have time for it right now.

  • In the meantime, if the values inside the geokey_dir container / sequence were known at compile time, then try to see if you could use ::frunk 's hlists to construct a sequence able to hold values such as [_42, _1026], etc.

Oh dang, so maybe my whole approach is wrong. What would be a more rusty approach, or: what would ferris do?

Maybe indeed save my data in a hashmap? I just thought saving it in the enum was a nice solution. Unfortunately I do not know the constants at compile time as this file is loaded during run time. Again, thanks for the work you put into this!

Hey @Yandros I really appreciate the time you have taken to help me out.
I don't want to waste so much of your time but if my approach is not doable in Rust (or not idiomatic), could you maybe point me to another/better way of modelling the data I have to work with in Rust?

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.