How do I constrain a type parameter to any integer type?

I think the error messages (mostly) all indicate the same problem. My type parameter T needs to be some integer type. The code deals with small integers, so besides i8 and u8, any of the integer types will work. Is there a way to add a constraint that tells the compiler that T is any integer type? Am I really thinking about this in the right way?

Thank you.

Code and error message below.

use std::clone::Clone;

fn do_stuff<T>(nl : &[T]) -> Vec<T>
    where T : Clone
{
    let sz = nl.iter().cloned().product() as usize;
    let mut mask : Vec<T> = Vec::with_capacity(sz + 1);
    for i in 1..=(sz+1) as T
    {
        mask.push(i);
    }
    // Eliminate every multiple for each prime in the list
    for p in nl
    {
        for i in 1..=(sz / (*p) as usize)
        {
            mask[i * (*p) as usize - 1] = 0;
        }
    }
    mask
}

fn main() {
    let mask = do_stuff(&[2, 3, 5, 7]);
    for i in mask
    {
        print!("{} ", i)
    }
}

Error messages:

error[E0308]: mismatched types
 --> src/main.rs:8:18
  |
3 | fn do_stuff<T>(nl : &[T]) -> Vec<T>
  |             - this type parameter
...
8 |     for i in 0..=(sz+1) as T
  |                  ^^^^^^^^^^^ expected integer, found type parameter `T`
  |
  = note:        expected type `{integer}`
          found type parameter `T`
  = help: type parameters must be constrained to match other types
  = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters

error[E0308]: mismatched types
  --> src/main.rs:10:19
   |
3  | fn do_stuff<T>(nl : &[T]) -> Vec<T>
   |             - this type parameter
...
10 |         mask.push(i);
   |                   ^ expected type parameter `T`, found integer
   |
   = note: expected type parameter `T`
                        found type `{integer}`
   = help: type parameters must be constrained to match other types
   = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters

error[E0308]: mismatched types
  --> src/main.rs:16:43
   |
3  | fn do_stuff<T>(nl : &[T]) -> Vec<T>
   |             - this type parameter
...
16 |             mask[i * (*p) as usize - 1] = 0;
   |                                           ^ expected type parameter `T`, found integer
   |
   = note: expected type parameter `T`
                        found type `{integer}`
   = help: type parameters must be constrained to match other types
   = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters

error[E0605]: non-primitive cast: `usize` as `T`
 --> src/main.rs:8:18
  |
8 |     for i in 0..=(sz+1) as T
  |                  ^^^^^^^^^^^
  |
  = note: an `as` expression can only be used to convert between primitive types. Consider using the `From` trait

error[E0605]: non-primitive cast: `T` as `usize`
  --> src/main.rs:14:28
   |
14 |         for i in 1..=(sz / (*p) as usize)
   |                            ^^^^^^^^^^^^^
   |
   = note: an `as` expression can only be used to convert between primitive types. Consider using the `From` trait

error[E0605]: non-primitive cast: `T` as `usize`
  --> src/main.rs:16:22
   |
16 |             mask[i * (*p) as usize - 1] = 0;
   |                      ^^^^^^^^^^^^^
   |
   = note: an `as` expression can only be used to convert between primitive types. Consider using the `From` trait

error: aborting due to 6 previous errors

Some errors have detailed explanations: E0308, E0605.
For more information about an error, try `rustc --explain E0308`.

I don't know that there is a great out-of-the-box way to do that. I think you are mostly thinking about it the right way, but there isn't such thing as an Integer trait that you could use to constrain T: Integer. And I don't think that as will ever work on a generic type parameter. So that wouldn't work either.

Here is an interesting way to do it, that may or may not be good for your use-case ( probably not ):

(playground)

trait DoStuff<T> {
    fn do_stuff(&self) -> Vec<T>;
}

macro_rules! impl_do_stuff {
    ($type:ty) => {
        impl DoStuff<$type> for [$type] {
            fn do_stuff(&self) -> Vec<$type> {
                let sz = self.iter().cloned().product::<$type>();
                let mut mask = Vec::with_capacity(sz as usize + 1);
                for i in 1..=(sz + 1) {
                    mask.push(i);
                }
                // Eliminate every multiple for each prime in the list
                for p in self {
                    for i in 1..=(sz / p) {
                        mask[(i * p - 1) as usize] = 0;
                    }
                }
                mask
            }
        }
    }
}

// Add whatever types you want here
impl_do_stuff!(i16);
impl_do_stuff!(i32);
impl_do_stuff!(u16);
impl_do_stuff!(u32);
impl_do_stuff!(usize);

fn main() {
    let mask = &[2, 3, 5, 7].do_stuff();
    for i in mask {
        print!("{} ", i)
    }
    
    println!("");
    
    let mask = &[2u16, 3u16, 5u16, 7u16].do_stuff();
    for i in mask {
        print!("{} ", i)
    }
    
    println!("");
    
    let mask = &[2usize, 3usize, 5usize, 7usize].do_stuff();
    for i in mask {
        print!("{} ", i)
    }
}

The trick is to create a DoStuff trait that that has the do_stuff function and that is automatically implemented by using a macro for whatever types that you want it to work with.

But likely you are better off just requiring a specific type of int. Depends on what you are using it for.

Will the num crate meet your needs? https://crates.io/crates/num

1 Like

You can't constrain generic types to other types. You can only require the types to implement a number of traits.

Generally functions that work with any integer types will be super tedious to specify, as you will have to require everything from std::ops and their outputs.

Consider using a macro, which behaves more like a C++ template, allowing any type that syntactically happens to work.

1 Like

The num crate provides an Integer trait, which is implemented on all the primitive integer types.

First, Thank everyone that responded. I will look at your suggestions.

After some pondering I remembered that Rust prefers to think in terms of what a type does, not what a type is. I was thinking, I want the type to be any of the integer types. Had I thought - I want T to do what integers do - I would have realized that I was over-constraining T.

The second thing Rust wants is for me to check all the details. In this case, Rust doesn't do implicit type conversions. So, this is what I came up with:

use std::convert::{From, Into};
use std::iter::Product;

fn do_stuff<T>(nl : &[T]) -> Vec<T>
    where T : Copy          // .cloned()
            + Product       // .product()
            + Into<usize>   // Conversions between T and usize
            + From<usize>
{
    let sz : usize = nl.iter().cloned().product::<T>().into();
    let mut mask : Vec<T> = Vec::with_capacity(sz + 1);
    for i in 1..=(sz+1)
    {
        mask.push(i.into());
    }
    // Eliminate every multiple for each prime in the list
    for p in nl
    {
        // Becasue into() can't infer the result without a hint
        let pp : usize = (*p).into();
        for i in 1..=(sz / pp)
        {
            mask[i * pp - 1] = 0.into();
        }
    }
    mask
}

fn main() {
    let mask = do_stuff(&[2, 3, 5]);
    for i in mask
    {
        print!("{} ", i)
    }
    println!();
}

Note the use of .into().

I'm still not completely satisfied with this solution. I don't know how to say that the traits Into and From provide methods that are bijective.

1 Like

With both Into<usize> and From<usize>, the only integer type that fits is usize and custom types with those impls.

2 Likes

D'oh!

I tried using the Integer trait, but does not seem to be something I can cast to and from usize.

1 Like

What you've shown me is that I can do something like what I was thinking with a macro. I don't want to do that in this case but I now know it's possible. Now I'm really just trying to get a feel for Rust. I am arriving at the belief that this function is not a candidate for implementation with a type parameter.

Thank you.

1 Like

Thank you. I think you saved be hours of eventual bafflement.

1 Like

Thank you. I gave it a shot, but I could not get it to work with my code.

1 Like

So I think the answer is that I should not try to implement this function or one like it with a type parameter. I can understand that. Having experience with C++, I know that there are things you can do, but you shouldn't do.

1 Like

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