Will this be optimized in release mode?

open in playground

I'm working on a function that takes a generic type then returns the reference of the responding field. To do this in safe rust, I have to via Any. It theoretically will cause performance overhead, but I don't know whether the compiler will help me to optimize it out.

use std::any::{Any, TypeId};

struct MyData {
    a: String,
    b: Vec<u8>,
    c: u32,
}

impl MyData {
    #[inline(always)]
    fn get_raw(&self, id: TypeId) -> Option<&dyn Any> {
        if id == TypeId::of::<String>() {
            Some(&self.a)
        } else if id == TypeId::of::<Vec<u8>>() {
            Some(&self.b)
        } else if id == TypeId::of::<u32>() {
            Some(&self.c)
        } else {
            None
        }
    }

    #[inline(always)]
    fn get<T: 'static>(&self) -> Option<&T> {
        self.get_raw(TypeId::of::<T>())
            .map(|x| x.downcast_ref().expect("type mismatch"))
    }
}

fn main() {
    let my_data = MyData {
        a: "hello world".into(),
        b: vec![1, 2, 3, 4, 5],
        c: 200,
    };

    // Will this be optimized into
    // `println!("{}", &my_data.a);`
    // ?
    println!("{}", my_data.get::<String>().unwrap());
}

why are you doing this? There's most definitely a better way to do what you want, sure this is a xy problem

also, no i don't think that would get optimized since you're handling type information at runtime.

EDIT: this kind of "reflection" behaviour is really necessary, I'd do it like this: Rust Playground, not only is it waay safer and more ergonomic, I'm pretty sure it will render better optimizations

btw, this is the nightly Provider API, consumed via request_ref and request_value. It's used for generic member access where the set of types isn't necessarily known ahead of time, such as for reflection APIs or Error.

Doing so as an inherent method likely isn't actually what you want (just use multiple accessors instead), but in a generic interface it's definitely useful. (Even if it's fully monomorphized and isn't used dyn at all.)

I would say the optimizer is unlikely to see through any TypeId shenanigans, but... I'd be wrong. See this godbolt example: in retrospect it's clear that this should be fairly trivial to inline and constant propagate; after inlining it'll look something along the lines of

const STRING_ID: u64 = 0x582ef5d793ae4049;
const VEC_U8_ID: u64 = 0x13ec134f4e02c05f;
const U32_ID: u64 = 0xe1d507b6631aabce;

pub fn get_u32(x: &MyData) -> Option<&u32> {
    match U32_ID {
        STRING_ID => match U32_ID {
            STRING_ID => &x.a,
            _ => panic!("type mismatch"),
        }
        VEC_U8_ID => match U32_ID {
            VEC_U8_ID => &x.b,
            _ => panic!("type mismatch"),
        }
        U32_ID => match U32_ID {
            U32_ID => &x.c,
            _ => panic!("type mismatch"),
        }
    }
}

and the constant propagator loves code like that.

2 Likes

But if I call

println!("{}", my_data.field::<f64>("x").unwrap());

It doesn't pass the compilation. I hope it will return None.

It definitely compiles and returns None, if you just use f64 as generic parameter; and it can't be compiled no matter the parameter used, if you pass in an argument which isn't expected by field().

if you're up for using nightly you can do: Rust Playground

I personally really like this solution

Sure, I like it too. But stable rust doesn't support the features you use at present. I will modify when stable rust supports them. Thanks.