Home > database >  Cast a struct with a generic type parameter to a specific type
Cast a struct with a generic type parameter to a specific type

Time:10-26

Note: Earlier today I asked a similar question, but this one has a significant difference, so I am posting it as a separate question.

If I have two (or more) structs:

struct A {...}
struct B {...}

and a generic function fn f<T>(param: Vec<T>) that I call with passing a vector of A or B (or some other type for that matter), is there a way in that function to have something like this (pseudo code):

if param is Vec<A> {
    // do something with "param as Vec<A>", like this:
    let a: Vec<A> = (Vec<A>) param;
    // ...
}

Typically in OOP languages I could basically check the type of the vector parameter or one of its elements (given the vector is not empty) and cast the parameter.

In Rust, is there a simple and direct way to achieve that, w/o sacrificing performance and w/o the need to change the function signature or write any code outside the function (so no enum wrappers, traits, dyn/runtime "magic", etc.).

CodePudding user response:

You can still use the Any approach as shown in the linked question if you are able to constrain T: 'static

use std::any::Any;

struct A {}
struct B {}

fn f<T: 'static>(param: Vec<T>) {
    if let Some(_param_a) = (&param as &dyn Any).downcast_ref::<Vec<A>>() {
        println!("I am a Vec<A>");
    }
    else {
        println!("I am something else");
    }
}

fn main() {
    f(Vec::<A>::new());
    f(Vec::<B>::new());
}

This does not have any runtime cost.

CodePudding user response:

TL;DR

Use traits to represent what behaviour T is supposed to have:

struct Answer {}
struct Question {}
trait Summarizable { fn summarize(&self); }
impl Summarizable for Answer {
    fn summarize(&self) { println!("I'm an answer"); }
}
impl Summarizable for Question {
    fn summarize(&self) { println!("I'm a question"); }
}

fn main() {
    let posts: Vec<&dyn Summarizable> = vec![&Answer {}, &Question {}];
    for post in posts {
        post.summarize();
    }
}

I believe Rust to be sufficiently different from other languages, at the very least OOP-wise, so that usual reflexes we have in other languages are best left behind. Part of the spirit I understand from Rust is that we almost never operate in a "what are my values actually containing" mode.

Rather than try and divine the type of a value, Rust have beautifully crafted constructs to lift that guessing out of our hands and into the very structure of the code.

For instance, let's consider the match construct, with example from the Book:

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}

It is extremely simple, but the key idea here is that in all arms of the match, we know exactly what type we are acting on. And I believe this particular bit of information helps me solve the kind of problem at hand here, that is to try and act differently depending on the type of a value, because it prompts me to challenge the code I write that depends on such unknown state of values.

I have no reason to code while wondering what are my values because the language constructs allow me to write in a context where I know. In this case, it works by moving the code from a manual type checking :

fn main() {
    let posts: Vec<&dyn Summarizable> = vec![&Answer {}, &Question {}];
    for post in posts {
        // this is pseudocode
        if typeof post == Answer {
            println!("I'm an answer");
        }
        if typeof post == Question {
            println!("I'm a question");
        }
    }
}

To code that is specific to the behaviour that corresponds to that type:

impl Summarizable for Answer {
    fn summarize(&self) { println!("I'm an answer"); }
}
impl Summarizable for Question {
    fn summarize(&self) { println!("I'm a question"); }
}

While making the actual main function way clearer in terms of what is actually supposed to happen, that is to summarize the posts, not wonder about what they are:

fn main() {
    let posts: Vec<&dyn Summarizable> = vec![&Answer {}, &Question {}];
    for post in posts {
        post.summarize();
    }
}

p.s. I ignored the "no traits" part of your question because I find it unreasonnable. If one wants to write other languages, they can! But traits in Rust are an idiomatic way to solve the general "acting differently based on the type of a value" problem.

  • Related