Generic type function

"I'm trying to work with a generic function with the following signature fn geometric_mean(x: &[T]). However, I feel that Rust is not so efficient for it, as even though it's possible to identify the data type passed as a parameter during the function's execution, it doesn't allow me to handle that data. T can be an i32 or an f64, but I don't know how to make that explicit. My solution was to create the struct pub > trait Numeric {

    fn to_f64(&self) -> f64;
}

impl Numeric for i32 {
    fn to_f64(&self) -> f64 {
        *self as f64
    }
} 

Does anyone know an efficient way to handle these generic parameters? I don't want to have to iterate through the array just to convert it to an f64 or i32 array."

You can use the num crate if you want to write generic code over numeric types.

1 Like

Your feeling is wrong. Rust has one of the best generics designs.

I have zero idea what that means.

You don't. Your mistake is that you are trying to use generics for restricting to a concrete set of types. That's not what generics are for, and it is a code smell anyway. You should be doing what generics actually are suitable for: abstracting over an arbitrary set of types with specified capabilities.

Again, it is entirely unclear what you mean by this. Sou don't have to convert an entire array at once. You can iterate over a generic slice, and with the appropriate trait (eg. your own or the ones in num_trait previously recommended), you can perform the conversion on the individual elements.

For a generic function geometric_mean you'd need a trait that defines multiplication and square root, implement that for i32 and f64 and use this trait as a trait bound for geometric_mean.
Or, if you really insist on knowing the type of your list's elements, use an enum that has two constructors, one for i32 and one for f64.

In Java, with a declaration like:

List<MyType> list;

All references to <T> in the List class are automatically determined as MyType at compilation time, and Java does not prevent me from compiling. I can even encapsulate T within an interface and create new instances of T. I understand that Rust avoids this for security reasons because if MyType were an invalid type, it could lead to a runtime error. However, I miss having the option to handle this manually.

You can do the same in Rust, just manually by using an enum instead, that's the second possibility of my post above.

enum NumberType {
   Int(i64),
   Float(f32),
}

fn geometric_mean(x: &[NumberType]) {...} 

Apologies if I misunderstand what you are asking, but it occurred to me that you were in fact looking for the Any trait in order to perform downcasting. It’s not often the best option, but it’s definitely something you can do in Rust too. (You’re often better served by enums and/or well-defined traits as mentioned by others.)

I feel that Rust doesn't work as seamlessly with generics because I have to implement most of the functions myself in previously, even though f64 and i32 already have them implemented. Additionally, I have to create a conversion function to convert the <T> type myself, even though the compiler should already know the type <T> as soon as I provide the function parameter. Nevertheless, the issue is likely with me since I just started using Rust yesterday, so I may be missing something.

like this ?

pub trait Numeric {
fn to_f64(&self) -> f64;
}

impl Numeric for i32 {
    fn to_f64(&self) -> f64 {
        *self as f64
    }
}

impl Numeric for f64 {
    fn to_f64(&self) -> f64 {
        *self
    }
}

You never "convert" generics, you use a trait (bound). For "conversion" there are enums.

Sounds like you want C++ templates. Maybe you should send us a complete sample of what you would do in Java or C++ so we can better understand your question and suggest how it would be done in idiomatic Rust.

No, this is not downcasting.

in this case, i need cast one by one:

pub fn harmonic_mean(x: &[T]) -> f64 where T:Numeric{

    return x.len() as f64/x.iter().map(|e| 1.0/e.to_f64()).sum::<f64>();
}

i want some like this:

pub fn harmonic_mean(x: &[T]) -> f64 where T:Numeric{

    return x.len() as f64/x.iter().sum::<f64>();
}

That's another "problem" that has nothing to do with generics.

fn harmonic_mean(x: &[i64]) -> f64 {

    return x.len() as f64/x.iter().sum::<f64>();
}

Doesn't work either, because Rust does not do implicit conversions to float like Java (and C and C++ and ...).

Okay, I see! Then you are right: it’s not possible to exactly write this code in Rust.
As said by @Release-Candidate, Rust does not perform such implicit casts, and do not allow you to define your own implicit casts either. It’s indeed not directly related to generics. (If user-defined implicit casts were a thing, some trait defined in the standard library could have enabled you to use that in a generic way, but it’s just something you’ll never see in Rust anyway, by design.)

EDIT: by the way, casting a i32 value into a f64 value is (almost always) a lossy operation. You can’t exactly represent i32 values using f64 and that’s why neither From<i32> nor TryFrom<i32> are implemented on f64. The as conversion is allowed to approximate to the closest value instead of providing the exact same value in the target type. Rust was designed to force you to be deliberate about such operations. That’s the rationale. I hope it helps understanding why you are forced to define this operation yourself, in a deliberate way, like you did with your Numeric trait.

EDIT 2: back to the original post.

Note that you are iterating through the array exactly once, with or without the conversion. sum is ultimately driving the iterator (iterating) to the end anyway (though it may get optimized in various ways depending on the situation). By using map you are merely adding an operation to the for loop you could have written manually, but you don’t add a whole new loop, and in fact, you are not even creating a new array at all (at least in the code you provided in your last comment). Note that you could also perform the conversion on the result of sum() instead of converting and then summing the values. The latter may be more efficient and is giving a more precise result (though you have to be careful not to overflow). Well you see what I mean by "deliberate".

1 Like

No, that's not true. Rust also has type inference for generics if that's what you mean.

If it is not, provide a concrete code sample.

2 Likes

A (IEEE 754) 64 bit float can hold a 52 bit integer, you are thinking of a i64 in a f64 or i32 in a f32.

1 Like

This is wrong, i32 can be losslessy represented in a f64 and in fact f64 implements From<i32> (and TryFrom<i32> thanks to its blanket implementation). Going from i64 to f64 (or from i32 to f32) is however lossy and hence no From/TryFrom implementation is provided [1].


  1. Well, technically an implementation of TryFrom could be provided which fails when the i64 value can't represented exactly with a f64 ↩︎

Oops, indeed. if the bits for the exponent are all 0s, then f32 can encode the same numbers as a i24 and smaller and a f64 can encode the same values as a i52 and smaller. I was plain wrong here, thank you for pointing this out.

You are right. Relevant discussion from internals forum.

2 Likes