Converting a normal funciton to a generic function

The code below works for me but if I remove the comments around my generic version of push_3, I get the compile error.

It seems to me that rust is complaining that 3.0 is not of the proper type in the generic function but it works fine for the non-generic version and I do not understand why ?

use std::cell::RefCell;
use std::ops::DerefMut;

fn push_3(v : &mut Vec<f32>) {   
    v.push( 3.0 );
}

/*
fn generic_push_3<T>(v : &mut Vec<T>) {   
    v.push( 3.0 );
}
*/

fn main() {
    let v = RefCell::new( vec![1.0, 2.0] );
    push_3( v.borrow_mut( ).deref_mut() );
    println!( "{:?}", v  );
}

The problem is here:

fn generic_push_3<T>(v : &mut Vec<T>) {   
    v.push( 3.0 );
}

Here, the caller decides what T is, so generic_push_3 must work no matter what the caller chooses. What should this code do if I call generic_push_3::<String>? You're going to add 3.0 to a vector of strings?

3 Likes

Because with generic functions the caller will decide what T is going to be. So you cannot generally assume that it's going to be a float literal. Whatever is going to be pushed onto the Vec<T> needs to be of type T.

Since, when writing the generic function, you cannot know which concrete type T will be when the function is called, the only thing you can do within the function body is to rely on trait bounds that T satisfies, e.g.:

fn generic_push_3<T>(v : &mut Vec<T>) 
where
    T: Default,
{   
    v.push(T::default());
}

Notice, that we needed to limit the types that T can be to those implementing the Default trait by means of the where clause, so that we may assume, that T has a default() method to construct an instance of T.

In C++, the compiler determines (at compile time) that the usage is valid for all the cases that use it.

Is there a where clause that would make it work for all types that could convert from the integer value 3 to the type T ?

C++ doesn't have generics at all, it only have templates – thus it's useless to say what C++ does. It's entirely different thing which is only very superficially similar to generics.

Ada, Java, C#, Go, Haskell and all other languages that do have generics work more-or-less like Rust does.

No, because there are not “integer value 3” type. You may ask for a type to be constructible from i8 (i8: Into<T>) – but then u8 wouldn't be supported. Or you may ask for a type to be constructible from u8(i8: Into<T>) – but then i8 wouldn't be supported.

It's known that writing generic numerical computation code in Rust is real PITA, people usually just go with macros to implement them and not with generics.

It's actually a {float} value - either f32 or f64 - since the value is 3.0 and not 3.

In either case, though, yes, there is:

fn generic_push_3<T>(v: &mut Vec<T>)
where T: From<f64> {
  v.push(T::from(3.0));
}

You can also write the innermost conversion as (3.0).into(), if you prefer; Into has a blanket implementation based on From.

For complicated reasons, there isn't a numeric tower of types in Rust, so there's no type or trait that is implemented by all integers or by all floating point types. There are crates that provide that, if you want something more general, but working with the specific numeric type of interest, non-generically, is usually good enough.

3 Likes

The following macro version of push_4 works for me:

use paste::paste;
use std::cell::RefCell;
use std::ops::DerefMut;

# [macro_export]
macro_rules! push_4 {
    ($type_name: ident) => {
        paste! {
            fn [< push_4_ $type_name >] (v: &mut Vec<$type_name>) {
                v.push(4.0);
            }
        }
    };
}
push_4!( f32 );

fn main() {
    let v = RefCell::new( vec![1.0, 2.0] );
    push_4_f32( v.borrow_mut( ).deref_mut() );
    println!( "{:?}", v  );
}

The following generic version of push_3 works for me:

use std::cell::RefCell;
use std::ops::DerefMut;

fn generic_push_3<T>(v : &mut Vec<T>)
where T : From<i32> {
    v.push( T::from(3) );
}

fn main() {
    let v = RefCell::new( vec![1.0, 2.0] );
    generic_push_3( v.borrow_mut( ).deref_mut() );
    println!( "{:?}", v  );
}