How to use types as values in Rust? / Metatypes

841 Views Asked by At

I’m working on the database component of my app. The idea is to describe objects using a Record structure which stores an identifier for fetching the object, and also specifies the object type.

struct Record<'a, Object> {
    id: &'a str
    // TODO: another field for the object type?
}

Record should not store the object itself—just its type. A RecordFetcher would then use this information to retrieve an object from the database and cast it to the specified type.

trait RecordFetcher {
    fn fetch_object_for_record<Object>(record: Record<Object>) -> Option<Object>;
}

The Rust compiler refuses to build this code because of the unused generic parameter in Record’s declaration, and I’m confused about how to include this information in the structure.

1 | struct Record<'a, Object> {
  |                   ^^^^^^ unused parameter
  |
  = help: consider removing `Object`, referring to it in a field, or using a marker such as `PhantomData`
  = help: if you intended `Object` to be a const parameter, use `const Object: usize` instead

I mostly work with Swift, so here’s a functioning example of what I’m trying to achieve. Object.Type here is a metatype: it specifies the type without requiring an instance of it.

struct Record<Object> {
    let id: String
    let objectType: Object.Type // <- metatype
}

protocol RecordFetcher {
    func fetchObject<Object>(for record: Record<Object>) -> Object?
}

Is there a way to achieve the same result in Rust?

2

There are 2 best solutions below

1
On

In Rust typically you can utilize enums for this. A good example is serde_json's Value enum type. Another similar example would be mysql's Value enum type. In Rust each enum variant can have associated data of an arbitrary data type. So enums allow you to solve the problem with different types for the ID (in case you do not always have string IDs) or the value itself. That doesn't give you all you ask for though, only part of the solution.

Rust doesn't have reflection (read this), so I'm not aware of an easy, automated way to achieve the mapping between an object type and an enum variant (holding the actual object value).

2
On

You don't actually have to store the type in the Record struct. You can write

struct Record<'a> {
    id: &'a str
}

trait RecordFetcher {
    fn fetch_object_for_record<Object>(record: Record<'_>) -> Option<Object>;
}

But if you do want to make Record generic over the object's type, your question is actually answered by the error message: Use PhantomData.

struct Record<'a, Object> {
    id: &'a str
    _type: PhantomData<Object>,
}

Rust doesn't have metatypes or any other form of runtime reflection. But it does have a very powerful trait system, so you probably don't need reflection anyway.

However, I'm wondering why you need to implement this in the first place. Crates for accessing a database usually already have type-safe abstractions that you can use. What crate are you using?