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?
If
PartialOrd<u8> for i32
existed, what should this method's declaration look like?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!())
}
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.