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:
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.
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
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With