Home > front end >  How can I create a generic function that takes any signed or unsigned integer and converts it to a u
How can I create a generic function that takes any signed or unsigned integer and converts it to a u

Time:09-17

I came up with a function that is supposed to take any signed or unsigned integer and map it to a u8. Every negative value becomes 0 while every value above 255 should be 255. I know this is trivial, my goal is only to get an understanding on how generics work.

use std::convert::TryInto;

fn as_byte<T>(source: T) -> u8
where
    T: TryInto<u8>,
    T: PartialOrd<u8>,
{
    if source < 0 {
        return 0;
    }
    if source > 255 {
        return 255;
    }

    source.try_into().unwrap_or(0)
}

I thought it would make sense to constrain my generic to whatever I actually want to do with source, so I declared that I want to be able to compare it to an u8 and be able to do a conversion to u8 (which should not fail if I catch a value out of range before).

If I call as_byte(1234i32), it will fail since there is no implementation of PartialOrd<u8> for i32. Even if there were an implementation, this will fail due to source being moved on the first comparison and then being used after move in the second check.

If I pass source by reference like as_byte(&1234i32) it gets even worse. Now the TryInto<u8> trait is not implemented either, since there is no From<&i32> for u8.

I understand why these issues are raised, but I can not figure out how to resolve them. I tried to change the constraints to where &'a T or changed the parameter to source: &T, or both. That left me with similar issues where traits for reference types were not implemented. This leaves me feeling like I didn't understand the concept of the borrowing/references correctly in the first place.

Where is the error in my thought process?

  1. If PartialOrd<u8> for i32 existed, what should this method's declaration look like?

  2. Knowing that PartialOrd<u8> for i32 does not exist, how else should I constrain the type parameter? Why do I get the error "the trait `Foo` is not implemented for `&mut T`" even though T implements the trait? is related and somewhat answers my question - but I can (and probably should) not implement traits for primitive/std types, can I?

CodePudding user response:

You can write as_byte() like this:

fn as_byte<T>(source: T) -> u8
where
    T: TryInto<u8>   From<u8>   PartialOrd,
{
    if source < 0.into() {
        return 0;
    }
    if source > 255.into() {
        return 255;
    }

    source.try_into().unwrap_or_else(|_| unreachable!())
}

Playground

Since u8 is very small, it makes more sense to convert 0 and 255 to T and then compare than the other way around. Requiring PartialOrd<u8> makes as_bytes compile in isolation, but not when invoked with a concrete integer type. This is because Rust's integer types currently don't implement comparison to types of different width, you need to convert both operands to the same type.

I'm not sure why you had problems with source getting moved - PartialOrd defines comparison on &self and &rhs, so it should never cause a move.

  • Related