generics "specialization" for equal types

Viewed 45

I am trying to convert Rc<Vec<F>> into into Rc<Vec<T>> where T and F are numeric types like u8, f32, f64, etc. As the vectors may be quite large, I would like to avoid copying them if F and T are the same type. I do not manage to find out how to do that. Something like this -- it does not compile as the type comparison T == F is invalid:

fn convert_vec<F: num::NumCast + Copy, T: num::NumCast + Copy>(data: &[F], undef: T) -> Vec<T> {
    data.iter()
        .map(|v| match T::from(*v) {
            Some(x) => x,
            None => undef,
        })
        .collect()
}

fn convert_rc_vec<F: num::NumCast + Copy, T: num::NumCast + Copy>(
    data: &Rc<Vec<F>>,
    undef: T,
) -> anyhow::Result<Rc<Vec<T>>> {
    if (T == F) {        // invalid
        Ok(data.clone()) // invalid
    } else {
        Ok(Rc::new(convert_vec(data, undef)))
    }
}

The vector that I need to convert from is the response from a server which first sends the data type (something like "u8", "f32", "f64", ...) and then the actual data. At present, I store the vector with these data in enum like

pub enum Values {
    UInt8(Rc<Vec<u8>>),
    Float32(Rc<Vec<f32>>),
    Float64(Rc<Vec<f64>>),
    // ...
}

At compile time, I do not know in which format the server will send the data, i.e. I do not know F in advance. I do know T in every case I use it, but T might be a different type depending on the use case.

Using specialized functions like convert_rc_vec_to_f32 it is easy to handle the case where clone() is best. But that requires a separate function for each T with almost identical text. I am trying to find a more elegant solution than writing a macro or more or less repeating the code 9 times.

2 Answers

You should not try to prevent your function from being monomorphized with T and F being the same type, or even change its behavior in that case. Instead, you should not use it at all if it would be monomorphized in that case. This is possible because, if T and F were the same type, you would know it at compile time, so you could actually simply remove the function call at all.


It seems that you are actually storing all these vectors into an enum, which means you only know the actual type at run-time. But this doesn't mean my suggestion doesn't apply. Typically, if you wanted to get a vec of f32, you can do something like

match data {
    Float32(v) => v,
    Float64(v) => convert_rc_vec(v),
    UInt8(v) => convert_rc_vec(v),
    ...
}

If T and F both have a 'static lifetime, then you can use TypeId to compare the two types "at runtime":

if TypeId::of::<T>() == TypeId::of::<F>() {
    Ok(data.clone()) // invalid
} else {
    /* ... */
}

However, since this comparison happens "at runtime", the type system still doesn't know that T == F inside of this branch. You can use unsafe code to force this "conversion":

if TypeId::of::<T>() == TypeId::of::<F>() {
    Ok(unsafe {
        // SAFETY: this is sound because `T == F`, so we're
        // just helping the compiler along here, with no actual
        // type conversions
        Rc::<Vec<T>>::from_raw(
            Rc::<Vec<F>>::into_raw(data.clone()) as *const _
        )
    })
} else {
    /* ... */
}
Related