Complex Data Structure, with lots of structs

I am building an API with a fairly complex data structure inside. I
have worked round in circles thinking of the best representation. The
system has 50 or more different kinds of Axiom with different
structure. So I did this:

struct SubClass{sup:Class, sub:Class}
struct SubProperty{sup:Property, sub:Property}
//... 50 more

Now this causes two problems. First, if I want to store all of these,
I need a struct with lots of fields.

struct Ontology {
   subclass:HashSet<SubClass>,
   subproperty:Hashset<SubProperty>
}

And, secondly, although the types are different, they also contain
commonality. One solution would be to add a trait. This could address
the second question. Now I can add the commonality like so:

trait Annotated {
   fn annotations() -> Annotation
}
struct SubClass{sup:Class, sub:Class, annotations:HashSet<Annotation>}

impl Annotated for SubClass {
    fn annotations() {
        annotations
    }
}

struct SubProperty{sup:Property, sub:Property, annotations:HashSet<Annotation>}

impl Annotated for SubProperty {
    fn annotations() {
        annotations
    }
}

// 50 more...

But now I have a lot of boilerplate. And it doesn't solve the first
problem of storing all of these in another sturct.

Next solution was to put the commonality into a struct and the
diversity into a enum.

struct Axiom{
   annotation: HashSet<Annotation>
   body: AxiomBody
}

enum AxiomBody {
    SubClass{sup:Class, sub:Class},
    SubProperty{sup:Property, sub:Property}
    // 50 more...
}

This reduces the boilerplate. And I can store all the Axioms into a
single data structure HashSet<Axiom>. But now my data structure is
inefficent. If I want to get all of the SubClass I have to do
something like this:

axioms.iter().filter(|ax| if let AxiomBody::SubClass(_) = ax {true})

Now there is a way to solve this which is to store the variants
independently. I thought about a data structure like this

HashMap<Discriminant,Axiom>

which seems like a nice solution. But it isn't because
mem::discriminant needs an instance. So I can't get to the
SubClass axioms without creating an SubClass axiom instance.

So, I add another enum without values.

struct Axiom{
   annotation: HashSet<Annotation>
   body: AxiomBody
   kind: AxiomKind
}

enum AxiomKind {
    SubClass, SubProperty // 50 more...
}

enum AxiomBody {
    SubClass{sup:Class, sub:Class},
    SubProperty{sup:Property, sub:Property}
    // 50 more...
}

Now I can store my Axioms in a data structure HashMap<AxiomKind, Axiom> -- I just need to be careful putting data into the map, since
there is no way to enforce the relationship between key and the
value. This also contains a problem for the downstream user since even
when I return (say) all the instances of AxiomBody::SubClass, we
still have to use if let to access sup and sub because
AxiomBody::SubClass isn't a type -- I cannot directly return
instances of it.

So, I am stuck here and coming to the conclusion that what I need is
to macro the whole thing. So something like this:

axioms!{
  struct SubClass{sup:Class, sub:Class}
  struct SubProperty{sup:Property, sub: Property}
}

which will transform into:

enum AxiomKind {
  SubClass,
  SubProperty
  // 50 more...
}

struct SubClass
{
    sup:Class, sub:Class,
    annotated: HashSet<Annotation>,
    kind: AxiomKind
}

impl Annotated for SubClass {
   fn annotation(&self) {self.annotated}
}

impl Kinded for SubClass {
  fn kinded(&self) {self.kind}
}

// 50 more ...

I am slightly nervous about this -- I'm a lisp programmer and tend to
overuse macros. But I can't see anyway around a lot of boilerplate.

Am I missing something?

If anyone gets this far, thank you for taking this time with a long
post!

1 Like