Dynamic data structure

Sorry if the title is a bit vague, it's hard to explain this in one sentence. I'm trying to implement a data structure that's dynamic by nature. In essence, there are only "fields". Each field has a name and type:

struct Metadata {
    name: String,
}

enum Field {
    Text(String, Metadata),
    Image(String, Metadata),
    Boolean(bool, Metadata),
}

I also added support for lists:

enum List {
    TextList(Vec<String>, Metadata),
    ImageList(Vec<String>, Metadata),
    BooleanList(Vec<bool>, Metadata),
}

As you can see, lists don't support mixed types. That's exactly what I want. However, I do want to be able to have a list of "groups", where each group is some kind of wrapper able of holding multiple types. For example, let's say I'm creating a data structure that represents a grocery list. I'd need a list of groceries, and each grocery list item should have a name and a boolean to indicate whether it's checked off or not.

But as I said, it's dynamic by nature, so I'm running into problems:

struct Group {
    fields: Vec<Field>,
}

enum Field {
    // ...
+   Group(Group, Metadata),

enum List {
    // ...
+   GroupList(Vec<Group>, Metadata),
}

Now when I use List::GroupList, I can have groups of mixed types. However, all groups in a list need to adhere to a schema in the context of that list. So in my grocery list example, I can't be sure I'll have the same exact fields for each list item.

Am I approaching this the wrong way?

I hope what I'm trying to achieve came across. I found it pretty hard to explain.

What you are implementing sounds close to serde_json::Value, with the exception that serde_json::Value allows heterogeneous lists, whereas you want homogeneous ones.

pub enum Value {
    Null,
    Bool(bool),
    Number(Number),
    String(String),
    Array(Vec<Value>),
    Object(Map<String, Value>),
}

It almost sounds like you are implementing the object management part of a programming language runtime (e.g. like the Python interpreter or JVM).

I think the big piece of friction you are encountering is that an enum only works with a finite set of types, but your List and Field types could contain an infinite number of types (e.g. a field could contain a List<Text> or a List<List<Text>>, and so on). This is usually where traits and type erasure comes in.

I'm not sure exactly how you are wanting to use this data structure, but maybe you could create some sort of Value trait that everything implements, then give that trait methods with your business logic or add some sort of fn as_any(&self) -> &dyn Any + 'static method to enable downcasting to a concrete type.

5 Likes

I'm actually implementing something quite similar to ACF (advancedcustomfields.com). Your comment provided some good insight, thanks! I'm going to read a bit on downcasting and such :slight_smile:

1 Like

I've been thinking about this a bit more and came to a realization that what I want is actually not possible using just Rust's type system, if I'm not mistaken. Rust's type system is designed to be static, however, I want to leverage it to handle dynamic types (at runtime), which is impossible.

I think I need to manually evaluate things in my project. Maybe I can look at how Serde does this.

That's is what I had in mind when I said this:

One way of looking at the problem is you'd like to create and manipulate classes purely at runtime. That's probably overkill for what you want though, but you might be able to fudge it with something like serde_json::Value and doing a bunch of dynamic checks.

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.