Confusion interpreting error when using TryFrom with AsRef

I'm attempting to implement TryFrom<AsRef<str>> for my enum to convert from String or &str, or anything that is AsRef<str> really. I think the solution is probably to use std::str::FromStr instead, but I'd like to understand why my approach doesn't work, and how I might self-service with the error given to connect all the pieces.

The following code does not compile:

enum Num {
    One,
    Two,
}

impl<T> TryFrom<T> for Num
where
    T: AsRef<str>,
{
    type Error = &'static str;

    fn try_from(as_ref: T) -> Result<Num, Self::Error> {
        match as_ref.as_ref() {
            "1" => Ok(Num::One),
            "2" => Ok(Num::Two),
            _ => Err("unknown number"),
        }
    }
}

fn main() {
    let one: Num = "1".try_into().unwrap();
    let s = "2".to_string();
    let two: Num = s.try_into().unwrap();
}

And instead produces this error:

   Compiling playground v0.0.1 (/playground)
error[E0119]: conflicting implementations of trait `std::convert::TryFrom<_>` for type `Num`
 --> src/main.rs:6:1
  |
6 | impl<T> TryFrom<T> for Num
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: conflicting implementation in crate `core`:
          - impl<T, U> TryFrom<U> for T
            where U: Into<T>;

For more information about this error, try `rustc --explain E0119`.
error: could not compile `playground` due to previous error

The way I've interpreted the error is that somehow there's an implementation of Into for Enum when there isn't. I feel like there's one small piece I'm missing to finish this puzzle and I just can't find it. Any insight is much appreciated.

It's not that there necessarily is such an impl currently, but there may be one added in the future by some code that you don't control. The compiler has to conservatively protect against this possibility, or else the addition of such an impl could silently and unexpectedly break your code.

It's a combination of a couple things.

One is that coherence cares not just about avoiding current conflict / ambiguity, but also about preventing the possibility of future breakage due to a semver-allowable changes and ensuring crate compatibility. So that fact that no conflict is visible in the current crate isn't enough; we have to consider what downstream crates are allowed to do.

The other is that the checks are arguably too simple for various reasons such as

  • Avoiding negative reasoning, which has a tendenancy to make any sort of change (like a new implementation) a breaking change
  • Avoiding impl ... for ConcreteTypeA impacting impl ... for ConcreteTypeB
  • Avoiding downstream crates performing coherence overlap checks on their dependents, transitively

You can read about some of those from here forward (and in #30191).


So here we have

impl<T> TryFrom<T> for Num where T: AsRef<str>

And

impl<T, U> TryFrom<U> for T where U: Into<T> 
// Covers
impl<U> TryFrom<U> for Num where U: Into<Num>

But if I'm downstream from you, I'm allowed to implement both of, for example,

impl From<MyStruct<Num>> for Num { /* ... */ }
// The above also results in `Into<Num> for MyStruct<Num>`
impl<T> AsRef<str> for MyStruct<T> {}

So that's the potential conflict under today's rules.


It comes up with TryFrom, TryInto, From, and Into a lot because of their blanket implementations. The upshot is you need a helper conversion struct (not ergonomic) or you implement for specific types, not generically. Or you need specialization.

Here's an issue for TryFrom.

I hadn't considered that this was more of a limitation/protection from the compiler's perspective but it makes perfect sense the way you've laid it out. Thanks for the detailed response!

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.