Enum: equality, sub/super set troubles

#1

Hello!

I’m just starting out with Rust. Hell of a trip. Fun so far! Excuse the language.

I’m trying to make my own little recursive data structure, and I’m struggling with two things.

#[derive(Clone, Eq, PartialEq, Hash)]
enum TestStructure {
    Desc(String),
    Number(i64),
    //Visibility(f64),
    Row(Vec<TestStructure>),
    Table(HashMap<TestStructure, TestStructure>),
}

First, I want to mix some float in there, but due to the limitations inherent in dealing with floats, that seems hard to do. What’s the best way to resolve this? Do I create custom implementations for all of them, or can I selectively implement traits for Visibility only?

Let’s say that it makes sense for me to do so, and I’d be happy to define what equality and hashing of floats means in my particular domain.

Alternatively, I may be convinced to not use floats as keys in the map. In this case, how would I disallow floats as keys (but still use them as values), allowing the rest of them to be keys and/or values, and yet have them all remain the same type?

Secondly, let’s say that I have two versions of this structure in mind, one that makes sense when data arrives externally, one that makes sense when it is generated internally in the program. One is a subset of the other—the internal one is allowed one super special entry into the enum that can’t be allowed with data arriving from the outside, for security reasons. Otherwise they’re identical.

Can I define the subset enum and extend it as a new enum (or vice versa)? Or do I have to reimplement most of the enum twice?

1 Like

#2

Instead of f64, you could use noisy_float that implements Eq.

If not, then you have to implement Eq manually for the entire enum, since the derive() is all or nothing.

1 Like

#3

You can’t do this directly. What I usually do is put all the common variants in their own enum, and wrap that in another enum or struct, but you end up with something nested rather than flat. It does avoid implementing everything twice.

Another option is to have two enums and allow one to convert to the other, then your implementation in the second one can defer to the first via conversion.

1 Like

#4

My recommendation (not knowing much about your use case) would be not to use TestStructure as the key type. HashMaps indexing HashMaps seems like a conceptual nightmare. If you look at the very similar Value type in serde_json, you’ll see that the Object variant maps only strings to json values. If you need more than just strings, a custom Key enum that holds just the types that make sense in that position might be a good idea.

1 Like

#5

My unfortunate situation is that exactly that can happen, and it’s not something I can control. It’s also not something that is likely to happen very often, but it’s within the realm of possibility, and it’s not an error. I suppose it’s a weird metadata-like description where “these relationships are associated with this value”.

I intend to shift that into something that’s easier to work with further in the pipeline, but I do have to find a way to model it first.

0 Likes

#6

Then you’re probably best off using something like the noisy-float crate recommended above, so you can get non-NaN floats that can implement Eq. (If you need to have possible NaNs and implement Eq, then may God have mercy on your soul)

1 Like

#7

Is it possible to catch NaNs at least, or do I just resign myself to panics if I employ noisy-float?

0 Likes

#8

Looks like you want NoisyFloat (or N64 or R64) ::try_new(). As I understand it, panics should only happen if you later employ math on the numbers that would create NaNs. If you need to avoid that, then you might need to convert back to regular floats before performing operations on the values.

1 Like