Search code examples
rustrust-tokio

How to store an invariant type variable in Rust


I would like to parse the type of each value in a row of data from tokio-postgresql

Here is an example of getting a single value for a row of data from postgresql:

...
let rows = client
   .query("select * from ExampleTable;")
   .await?;

// This is how you read a string if you know the first column is a string type.
let thisValue: &str = rows[0].get(0);

In this example, it is known at design-time that the type in the first column is a string, and therefore the type for thisValue is &str. I would like to accept an invariant type.

I intend to use std::any::type_name::<T>() to derive the type name in thisValue and then use conditional logic (if/switch) to process this data differently depending on the type.

Is there an invariant way to store a variable in Rust? Will std::any::type_name::<T>() work on that variable? Is there another way to "box" the variable instead?

I understand that std::any::type_name::<T>() is using a kind of generics interface. To me, this means it's probably a compile-time strategy, not run-time. So I have my doubts that the way I am researching will work, but I hope I am on the right track and only need the final piece: an invariant type.

Should I be using &dyn Any and TypeId::of::<TypeHere>() == thisValue.type_id()?

In this situation, the get function of this API tokio-postgresql uses generics and doesn't return a boxed value. Therefore in this situation I may need to use columns() to determine the Rust type and the use separate functions to call get with different variables types.

The overall question still needs to be answered "How to store an invariant type variable in Rust", regardless of the specifics I have used to ask the title question.


Solution

    1. The preferred invariant type is &dyn any

    With &dyn any: any is the trait, dyn means the type of the trait.

    Declaring:

    let thisValue: &dyn Any = rows[0].get(0); //This works if tokio-postgresql returned a "dyn any" type, which it doesn't
    

    Example of testing what type is referenced:

    TypeId::of::<String>() == thisValue.type_id() //type checking using TypeId of boxed value.
    

    Example of testing the type with downcast:

    if let Some(string) = thisValue.downcast_ref::<String>() {
        println!("String ({}): {}", string.len(), string);
    }
    
    1. Boxed

    Box to force heap allocation (if necessary). This strategy is included too, so you can see how &dyn Any works with Boxed

    A "boxed" value of dyn Any is invariant:

    let thisValue: Boxed<dyn Any> = rows[0].get(0);  //This works if tokio-postgresql returned a "dyn any" type, which it doesn't
    

    In this scenario the API being used requires generic inference, so for tokio-postgresql this won't work, but it is the answer for the title question.

    An example of testing the type with the downcast function of Boxed:

    if let Ok(string) = thisValue.downcast::<String>() {
        println!("String ({}): {}", string.len(), string);
    }
    

    About the postgresql sub-problem

    As per the original post,

    In this situation, the get function of this API tokio-postgresql uses generics and doesn't return a boxed value. Therefore in this situation I may need to use columns() to determine the Rust type and the use separate functions to call get with different variables types.

    So this answer solves the question, and although it won't work with the tokio-postgresql API, it equips you with the knowledge of the kind of API you would like to find/build/wait-for.