I was trying to implement a TryFrom
trait for a custom type and came up on this interesting issue:
My new type wraps a NonZeroU16
. So I implemented TryFrom<u16>
first, no issues there:
use std::num::NonZeroU16;
#[derive(Debug)]
pub struct MyStruct(NonZeroU16);
impl TryFrom<u16> for MyStruct {
type Error = <NonZeroU16 as TryFrom<u16>>::Error;
fn try_from(value: u16) -> Result<Self, Self::Error> {
Ok(MyStruct(NonZeroU16::try_from(value)?))
}
}
Since try_from is fallible anyway wouldn't it be nice to have it generic over all possible "numeric" types? Let's try that and replace the imlp
with a generic one:
impl<T> TryFrom<T> for MyStruct
where
NonZeroU16: TryFrom<T>,
{
type Error = <NonZeroU16 as TryFrom<T>>::Error;
fn try_from(value: T) -> Result<Self, Self::Error> {
Ok(MyStruct(NonZeroU16::try_from(value)?))
}
}
Looks like we were a little too generic with our impl
, we get an error:
error[E0119]: conflicting implementations of trait `std::convert::TryFrom<_>` for type `MyStruct`
--> src/main.rs:28:1
|
28 | impl<T> TryFrom<T> for MyStruct
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: conflicting implementation in crate `core`:
- impl<T, U> std::convert::TryFrom<U> for T
where U: std::convert::Into<T>;
Looks like if some type U
implements std::convert::Into<T>
then it gets TryFrom<U>
"for free", which conflicts with our implementation. Ideally we would exclude such types by adjusting the where clause with something like:
where
NonZeroU16: TryFrom<T>,
T: !Into<NonZeroU16>,
... but that is not a valid syntax today.
Side question: Are there any plans/RFCs to include such a thing?
We will need to restrict the type T another way.
Surely we should be able to at least extend our generic implementation to some predefined set of types. Lets create our own "marker trait" for that and try (starting with our original set of 1 containing only u16
):
trait NZConvSource {}
impl NZConvSource for u16 {}
impl<T: NZConvSource> TryFrom<T> for MyStruct
where
NonZeroU16: TryFrom<T>,
{
type Error = <NonZeroU16 as TryFrom<T>>::Error;
fn try_from(value: T) -> Result<Self, Self::Error> {
Ok(MyStruct(NonZeroU16::try_from(value)?))
}
}
But a big surprise, unfortunately we still get the same error:
error[E0119]: conflicting implementations of trait `std::convert::TryFrom<_>` for type `MyStruct`
--> src/main.rs:17:1
|
17 | impl<T: NZConvSource> TryFrom<T> for MyStruct
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: conflicting implementation in crate `core`:
- impl<T, U> std::convert::TryFrom<U> for T
where U: std::convert::Into<T>;
Weird. Shouldn't our new impl be equivalent to the original non-generic one? The only type implementing our new bounding trait NZConvSource
is u16
and surely u16
doesn't implement Into<NonZeroU16>
.
What's even weirder: If we delete the impl NZConvSource for u16 {}
line we still get the same error ... for an empty set of types ?
What's the issue? Am I understanding trait bounds wrong? Is there some other current limitation on trait implementations I'm not aware of? Because the error message doesn't seem to be very helpful in this case.