Custom trait to convert generic type to usize

/// A value that can be converted into an usize.
trait IntoIndex <T>{

  /// # Panics
  /// This will panic if the resulting value won't fit into a `usize`, 
  /// typically because it contains a fraction or is outside of the
  /// range supported by `usize`.
  fn into_index(self) -> usize;
}

impl<T> Transform<Vec<T>> for Label
where
    T: Copy + IntoIndex<usize>, 
{
    fn transform(&mut self, input: Vec<T>) -> Vec<'&'static str> {

    let indices = input.iter().copied().map(|ix| ix.into_index());
    // ix will always be u32, f32 or lower than this (u16, u8)
    // In case of f32 it won't be a fraction will look like this [4.0, 13.0, 7.0, 9.0, ...]
    }
}

I want to create a custom trait that can convert generic type to usize.

How should this generic type be converted to usize? For instance, if I have the following:

struct Data {
    name: String,
}

How would this struct be converted to usize? By its name.chars()?

@Knarkzel, That's why I mentioned it will always be f32, u32, u16, u8, the values that can be converted to usize. Even in the case of f32, it won't be a fraction. It will be like (4.0, 13.0, 7.0, 9.0, ...)

You don't need generics for this if you know exactly the kind of types to implement this trait for. Here's one example using macros:

trait IntoIndex {
    /// # Panics
    /// This will panic if the resulting value won't fit into a `usize`, typically because it
    /// contains a fraction or is outside of the range supported by `usize`.
    fn into_index(self) -> usize;
}

macro_rules! into_index {
    ($($type:ident),*) => {
        $(impl IntoIndex for $type {
            fn into_index(self) -> usize {
                self as usize
            }
        })*
    };
}

into_index!(u8, u16, u32, f32);

fn main() {
    let items: Vec<u16> = (0..10).collect();
    let indexes: Vec<usize> = items.into_iter().map(u16::into_index).collect();
}
1 Like

Note that Rust supports systems where usize is u16, which is why, for instance usize::from is not implemented for u32.

It really depends on how much care you want to take around the floats. I see your comment says it should panic if there's a fractional part. Integers are exactly representable as floats, until you run out of significant digits.

    println!("{:.1}", 16_777_214_u32 as f32); // 16777214.0
    println!("{:.1}", 16_777_215_u32 as f32); // 16777215.0
    println!("{:.1}", 16_777_216_u32 as f32); // 16777216.0
    println!("{:.1}", 16_777_217_u32 as f32); // 16777216.0 !!
    println!("{:.1}", 16_777_218_u32 as f32); // 16777218.0
    println!("{:.1}", 16_777_219_u32 as f32); // 16777220.0 !!

This also means that at about half that size, you're going to drop all of the fractional parts.

    println!("{:.1}", 8_300_000.5_f32); // 8300000.5
    println!("{:.1}", 8_400_000.5_f32); // 8400000.0 !!

If you don't care about either of those, but still want to panic if there's a fractional part within the significant digits, you can just try a round trip or test fract() to see if it's 0.0.

Here's a couple of approaches. I found it cleaner to include a non-panicking version as well. I dropped the type parameter on the trait because I didn't see a need for it.

2 Likes

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.