Looking for macro/trait to get value out of struct by type (i.e. `typemap` without the HashMap)

Hello everybody,

I'm looking for a crate that has functionality to get values out of structs by type (e.g. a derive macros + helper traits).

Something like:

use get_value_by_type::*;

#[derive(GetValueByType)]
struct MyStruct {
    value: i32,
}

let my_struct = MyStruct { value: 42 };
let my_i32: i32 = my_struct.get_value_by_type();
// or:
let my_i32 = my_struct.get_value_by_type::<i32>();

I know, the various typemap crates on crates.io offer this functionality, but as far as I know, they are all based on a HashMap-like type.

If everything is known at compile-time, these typemap crates add avoidable overhead, therefore I'm looking for alternatives.

So far I've found the following crates, that would enable what I want:

These crates would work, but I'm pretty sure, that what I try to archive can be done in a simpler, more ergonomic way, with better error messages if something bad happens. As far as I know, the type-level programming magic that at least Frunk seems to apply is known for horrible error messages (please correct me, if I'm wrong).

Relevant keywords seem to be Row Polymorphism and Structural Typing.

Getting values out of a struct by a type alone would be good enough, but (optionally) getting values via a field name / type pair would be useful for cases where I want to store several values of the same type (and don't want to create a new type, manually). Frunk offers this, btw.

If there isn't anything simpler than Frunk and Rovvy – how would you implement this?

I might be wrong, but I think it could be done via a derive macro that implements a simple generic trait. Something like:

trait GetValueByType<T> {
    fn get_value_by_type_ref(&self) -> &T;
    fn get_value_by_type_mut(&mut self) -> &mut T;
}

impl GetValueByType<i32> for MyStruct {
    fn get_value_by_type_ref(&self) -> &i32 {
        &self.value
    }
    fn get_value_by_type_mut(&mut self) -> &mut i32 {
        &mut self.value
    }
}

...and then I could create a function like this:

fn my_fn(x: impl GetValueByType<i32>) -> i32 {
    let my_i32: i32 = x.get_value_by_type_ref();
    my_i32 + 1
}

This sounds like the Provider API.

6 Likes

Yes, the upcoming Provider API seems to be exactly what I need. Unfortunately it is not stabilized yet.

An earlier revision of the provider API is available as the dyno crate, and an earlier revision as object_provider.

If the solution is not required to be dyn compatible, then a common pattern is to just directly provide a fn get_field<T: 'static>(&self) -> Option<T> method with a body of roughly

if let Some(value) = (&self.value as &dyn Any).downcast_ref() {
    return Some(value);
}
// etc
None

This is essentially what the provider API is doing but via static monomorphization instead. You might spot this in the wild as a downcast_ref method which can downcast to multiple possible types.

The static interface needs to be duplicated for by-value, by-ref, and by-mut. The provider interface uses a clever trick to allow getting &T or T from the same interface, but &mit will always require a different receiver.

You may also be interested in the approach taken by the bevy_reflect crate.

Thank you, @CAD97!

It seems, these options lack the "static nature" (not sure if that is the right wording) of what I'm looking for (that's also true for the upcoming Provider API, unfortunately, I just realized). I.e., they return Option<T>, instead of T.

What I try to archive is basically: Instead of defining what actions a type can do (i.e., regular traits), I want to define what data a type can provide.

So kind of like traits for the structure of the contained data of a type.

This would enable a more functional flavor of compossibility.

Instead of traits that define what actions a type can do, freestanding functions could be defined, that act on types that provide certain data (which would be requested via trait bounds).

As an example, I'm currently building an application that collects data from different website. The process is often very similar:

  • Request data
  • Check if we need to log in
  • If we need to log in, do so, and request the data again
  • Process data
  • Display data / create alerts

I could try to create a trait for that (and I did). But in practice that would lead to tight coupling of the code. However, every website has certain special cases, which would be painful to handle with such a trait (or several of them).

Basically, trait-based code would not be shorter than isolated implementations that duplicate code, but the trait-based solution would be much more complex and more difficult to alter, etc. (i.e., painful to work with).

Instead, I'd like to create freestanding functions that do certain tasks, that can be combined.

Having "traits for data" would make that much easier, I believe (because I just could pass in the context data, and let the freestanding function extract what it needs, and add data that it produces).

Basically, this would be a bit like a static plugin system.

And having something that returns Option wouldn't be a great fit here, because of potential runtime errors, which I'd like to avoid.

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.