Generic return from fn

Hi,

Problem: Let say I wanna pass a value into a function increment it and return the result:


fn add_one <T> (x: &T) {
  x += 1 as T;
}

fn add_one_x <T> (x: T)-> T {
  x += 1 as T
}

fn main(){

  let mut x = 5i32;
  //  I expect the x will be 6 after add_one 
  add_one::<i32>(&x);
 //  I expect the y  will be 7 after add_one_x
 let y =   add_one_x::<i32>(x);
 

 println!("{}", y)


}

What would be a correct way to do this ?

use std::ops::{Add, AddAssign};
use num::One;

fn add_one <T: AddAssign + One> (x: &mut T) {
    *x += T::one();
}

fn add_one_x <T: Add<Output = T> + One> (x: T) -> T {
    x + T::one()
}

fn main() {
    let mut x = 5i32;
    
    //  I expect the x will be 6 after add_one 
    add_one(&mut x);
    
    //  I expect the y  will be 7 after add_one_x
    let y = add_one_x(x);
    println!("{}", y)
}

You will also need to add the num crate to you Cargo.toml

1 Like

ok this works but it seams a bit excessive ... might I ask for a bit of explanation. My intention was to pass a value regardless whether it is i32, i64, i16, etc. and increment it , but what if i wanted to multiply, divide of pass a float or increment by 4 ... Is there a more generic way to do this ? (or am i asking stupid questions :frowning: )

To explain why the changes are required:

If you define a function as fn foo<T>(x: T), you're effectively saying 'this function can take literally any type'. But looking at the implemention, that's not true - your function will only work with types that can be added, and that can represent the number one in some form. So you need to make those constraints known to the compiler - this is possible to do without external crates, but would require a lot of boilerplate that num implements for you.

Here's a translation of the type constraints to plain English:

  • T: AddAssign + One: T must be a type that:
    • Implements the trait AddAssign (which is used to implement Rust's += operator).
    • Implements the trait One (which is implemented by the num crate for all numeric types that can represent the number one).
  • T: Add<Output = T> + One: T must be a type that:
    • Implements the trait Add (which is used to implement Rust's + operator) in such a manner that the output of the addition is also of type T.
    • Implements the trait One (see above).
3 Likes

I see ... and it makes sense, with generics one needs to be careful ... thnx

Here's a cute trick you could do for integers (without the num crate):

use std::ops::Add;

fn add_one_x <T: Add<Output = T> + From<bool>> (x: T) -> T {
    x + true.into()
}

All of the integer primitives implement From<bool>, so true will get converted into the 1 of the appropriate type.

2 Likes

This won't work for floats, and other numeric types defined in other crates, so I wouldn't use it. Better to use From<u8> if you don't want the dependency. This will cover all numeric types in std except i8

1 Like

You mean to use it only with numeric types, but Rust doesn't know that. When you write a generic type T without constraints, Rust literally allows it to be any type. It allows it to be a String, a reference, an array of 17 File handles, an image of a cat. And + and as casts aren't guaranteed to work for all of these, so your function is rejected.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.