Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

simple rust generic/template add function

So I am just learning rust - I have setup the tools and got it all structurally working, cross compiling, linking libraries and so on... but now I am exploring the language after a few simple functions I now want to write some templates (I have a c++ background). So in c++ talk I want to do this:

C++

template<typename A, typename B>
auto add(A a, B b)
{
    return a + b;
}

int main() {
    std::cout << "add int int:    " << add(1, 2) << std::endl;
    std::cout << "add int double: " << add(1, 2.3f) << std::endl;
    std::cout << "add int float:  " << add(1, 2.3) << std::endl;
    std::cout << "add char int:   " << add('g', 2.3) << std::endl;
    std::cout << "add str str:    " << add(std::string("bob"), std::string("fred")) << std::endl;
}

output:

add int int:    3
add int double: 3.3
add int float:  3.3
add char int:   105.3
add str str:    bobfred

This adds to "things". If you try to add two things that don't implement the correct + operator with the correct operands you will get a compile error (like adding a string and an int) - pretty simple.

Rust

Now to rust (and I totally have not got the hang of it.

Normal add function

pub fn add(a: u64, b: u64) -> u64 {
    return a + b;
}

ok so far.

attempt 1 to template this I got to this implementation by listening to the errors and using the advised change (using std::ops::Add<Output = T> I sort of understand it, but I don't really know why I must specify this.

pub fn add<T: std::ops::Add<Output = T>>(a: T, b: T) -> T
{
    return a + b;
}


println!("add int int:   {}", add(1, 2));    // ok
println!("add int float: {}", add(1, 2.3));  // fails to compile

This is expected since I only told it about one type.... so now try for two types A,B - but this is where I get lost:

pub fn add<A: std::ops::Add<Output = A>, B: std::ops::Add>(a: A, b: B) -> A
{
    return a + b;
}

The error I get is:

error[E0308]: mismatched types
  --> utils/src/lib.rs:19:16
   |
17 | pub fn add<A: std::ops::Add<Output = A>, B: std::ops::Add>(a: A, b: B) -> A
   |            - expected type parameter     - found type parameter
18 | {
19 |     return a + b;
   |                ^ expected type parameter `A`, found type parameter `B`
   |
   = note: expected type parameter `A`
              found type parameter `B`
   = note: a type parameter was expected, but a different one was found; you might be missing a type parameter or trait bound
   = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.

So now I am totally lost. First I seem to have to specify a return type - but I want that to be deduced for me. How do I do that? The rest of the errors are probably all due to the return type being A - but I appear to have to specify something... any help here would be great! - I want to archive the same basic functionality of the c++ template above if possible

like image 526
code_fodder Avatar asked Sep 04 '20 21:09

code_fodder


Video Answer


1 Answers

First of all, you better refer to the documentation of std::ops::Add first. There's a type Output which you've set, but there's also a template parameter Rhs that you can set too. So you may try:

pub fn add_to_rhs<A, B>(a: A, b: B) -> B
  where A: std::ops::Add<B, Output = B>
{
    return a + b;
}

There you declare that LHS value should be addable with RHS value, producing a type of RHS value at the end. But this still won't work as you expected

println!("add int float: {}", add_to_rhs(1, 2.3)); 
// fails to compile, no implementation for `{integer} + {float}`

And this is reasonable, because weak typing is not for Rust, since it's prone to logical errors. What is a possible workaround? Practically analogous to what you would do in a non-generic program -- you would convert one of the values explicitly:

pub fn add_to_rhs<A, B>(a: A, b: B) -> B
where A: Into<B>, B: std::ops::Add<Output = B>
{
    return a.into() + b;
}

That doesn't achieve the same flexibility as C++ SFINAE (no weak typing, remember), since in order to add any values you would need to make two versions of a function; I showed a variant where an LHS value is casted and added to RHS type, but you need the other way around as well.

And since we have no overloading in Rust, well that becomes even less convenient, you'll have to call different functions and keep that in mind.

EDIT: I played a bit, and managed to have a single function for different mixes of types, but using it forces you to specify a return type every time

struct Adder<T>(std::marker::PhantomData<T>);
impl<T> Adder<T> {
    fn add<A, B>(a: A, b: B) -> T
      where T: std::ops::Add<Output = T>, A: Into<T>, B: Into<T> {
        return a.into() + b.into();
    }
}


println!("add int int:   {}", Adder::<i32>::add(1, 2));
println!("add int float: {}", Adder::<f64>::add(1, 2.3)); 
println!("add float int: {}", Adder::<f64>::add(2.3, 1));

The purpose of a struct is to separate add's template parameters (which can be fully deduced) from the resulting template parameter (which you would need to specify, otherwise the return type is ambiguous)

like image 173
Alexey Larionov Avatar answered Oct 07 '22 01:10

Alexey Larionov