Please help with generics

Greetings everyone!

Apologies for the dumb question. I just started the Rust official book, and everything seems so hard now (compared to Python, there is a whole new world in Rust). I need your help with generics. I want to write a simple function that adds 5 to whatever type is put into the function (integers and float obviously).

use std::ops::Add;
fn main() {
    let mut x1 = 20;
    let res= add_two(&x1);
    println!("The result is {}", res);
}

fn add_two <'a, T>(first: &'a T) -> &'a T 
where T: Add
{
    return first + 5;
}

If I understood it correctly, in case I need to add something to this has to implement the add trait (correct me if I'm wrong).

When I run the code the compiler told me "error[E0369]: cannot add {integer} to &T".
help: consider extending the where clause, but there might be an alternative better way to express this requirement.

Could you please help with explanations "on fingers" how to implement that function correctly?
The compiler offers me to add &T: Add<&i32, Output = &'a T> however, I have to understand why I need to do so and whether the simpler option exists in this case.

Thank you all in advance!

Currently, your method is defined to return a reference, but that's not possible because you create the value inside the function. You can never return references to values created inside a reference.

Anyway, if we remove the ampersands, then you might write this:

fn add_two<T>(first: T) -> T
where
    T: Add<i32, Output = T>,
{
    return first + 2;
}

This requires that T is a type such that you can add it with an i32 and get back a value of the same type. The only type that satisfies those constraints is i32, so you would only be able to call the method with an i32 as currently written. (Okay, yechnically you could also write your own custom type that would work.)

A better option is probably the following:

fn add_two<T>(first: T) -> T
where
    T: Add<Output = T>,
    T: From<i32>,
{
    return first + T::from(2);
}

Here, you require that T is a type such that you can add it together with itself, and such that any value of type i32 can be converted into an T. This allows a bit more things, but is still quite restrictive since only i32 and i64 satisfies those constraints. (types such as i16 do not work since not all i32 values can be converted into an i16.)

It might be better to write this instead:

fn add_two<T>(first: T) -> T
where
    T: Add<Output = T>,
    T: From<u8>,
{
    return first + T::from(2);
}

This would allow all integer types except for i8.


All that said, using generics with integers is generally always cumbersome in Rust. You might try the Num trait from the num crate, which comes with support for a lot of different operations out of the box.

I recommend that you try practicing generics with something other than numbers.

5 Likes

I’d like to note that while writing code generic over numeric types in Rust is possible, its cumbersomeness means that it’s not the simplest thing to do, so maybe it’s not the nicest exercise for learning Rust, especially at a beginner level. If you’re just learning about generics and simple traits, you might at least want to postpone it for later.

Any nontrivial function that’s supposed to be generic over numeric types quickly involves a large number of trait bounds, or it requires you to study and use traits and API of additional crates such as num (or you could also write your own trait and a bunch of implementations, but that might in turn become cumbersome - if you want to support all primitive numeric types - or involve using macros). Fortunately, generic functions over numeric types aren’t necessary in too many situations anyways.


Three more little remarks as you’re learning Rust and I noticed in your code:

  • If you like, you can use the “relatively” new println!("The result is {res}"); syntax which can be slightly less cumbersome to type and read. I though I’d mention it, because it might not have made its way into the book yet.

  • Also, I recommend using clippy when writing code. It suggests useful things such as

    warning: unneeded `return` statement
      --> src/main.rs:10:5
       |
    10 |     return first + 5;
       |     ^^^^^^^^^^^^^^^^
       |
       = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_return
       = note: `#[warn(clippy::needless_return)]` on by default
       = help: remove `return`
    

    While you wouldn’t have gotten this feedback from clippy on your code in particular, as it doesn’t compile successfully in the first place, it teaches a few useful patterns and you would’ve probably already come across suggestions to remove unnecessary returns (and maybe also other things) if you used it.

    I commonly like to configure my IDE to use cargo clippy instead of cargo check, and I also like to use a code-watching tool such as bacon (after installation invoked to use clippy with bacon clippy) in a second window for convenient display of full error messages while writing code.

  • Finally, it’s useful to use rustfmt (via cargo fmt, or e.g. via the “Rustfmt” button under “TOOLS” in the playground to format code you submit on a forum, since a standard formatting style improves readability.

5 Likes

Steffahn, thanks for the explanations!
So far Rust seems like a castle I need to conquer. It's hard but interesting.

Alice, thank you for such a detailed explanation! People didn't lie telling the Rust community is kind and always ready to help. Much appreciated!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.