Covered Implementations in Rust for Rustaceans

Hey folks, I am making my way through "Rust for Rustaceans" by Jon Gjengset, and got stuck on the following example from the "Covered Implementations" in Chapter 2 on Types.

The book says the following is a valid implementation:

impl<T> From<T> for MyType

However, when I tried putting this into a rust program of my own, it fails to compile with the following error.

   Compiling playground v0.0.1 (/playground)
error[E0119]: conflicting implementations of trait `From<MyType>` for type `MyType`
 --> src/main.rs:6:1
  |
6 | impl<T> From<T> for MyType {
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: conflicting implementation in crate `core`:
          - impl<T> From<T> for T;

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

Playground link here.

Now, I can see from the error that there is an impl for From<T> which is generic over T. However, it seems weird that this core implementation would apply here, as T is not necessarily MyType, which seems like it would have to be the case for the referenced core implementation to apply.

Is the conflict because there is a single shared case where both might apply - that is, when T is MyType? Is there something else going on? Is the book steering me wrong here, or am I misunderstanding the passage? I checked the errata for the book and didn't see this discussed, so I assume I'm missing something.

trait Trait {}
impl<T: Trait> From<T> for MyType {
    fn from(value: T) -> Self {
        Self
    }
}
// Just don't do this, otherwise the same error in From impl without T: Trait 
// impl Trait for MyType {}

This works.

2 Likes

It seems the chapter in the book only makes the claim that the implementation impl<T> From<T> for MyType, where From is trait from an external crate, and MyType is a type defined locally, is valid under Rust’s orphan rules.

Indeed, the implementation is not accepted, but that’s not due to orphan rules, but due to overlapping implementations. The overlap comes from the implementaiton

impl<T> From<T> for T

in the standard library.

It’s a bit suboptimal that this isn’t called out in the book, there probably could have been made a better example that actually compiles.


I’ve written a bit of a rambling on orphan rules myself the other day here.


The fact that the standard library’s impl<T> From<T> for T breaks your impl<T> From<T> for MyType implementation, even though both would be allowed without the other, highlights another relevant point of blanket impls such as impl<T> From<T> for T. They cannot be added at a later time, without potential for breakage. So with semantic versioning in mind, you’d call introduction of an implementation like impl<T> From<T> for T a “breaking change” and would require a major version increase, unless this implementation exists from the very beginning, i.e. the moment the trait From was defined. For the standard library, which doesn’t do major version increments, this is the case; the impl<T> From<T> for T implementation has always been there (the lack of a version number in the documentation indicates it was present since at least Rust 1.0).

6 Likes

Fun fact, this stuff can be surprisingly subtle. For example the introduction of From<T> for Option<T> in Rust 1.12 has been technically a breaking change. That breakage was either overlooked or considered acceptable (I would need to search up the relevant PR to know which one it was). It could be acceptable, with the argument that it’s unlikely that someone had already relied on code like the following to compile (and also sometimes Rust releases are tested against a large number of crates to determine whether something actually breaks existing code in practice)

struct Foo;

impl From<Foo> for Option<Foo> {
    fn from(foo: Foo) -> Option<Foo> {
        Some(foo)
    }
}

(see the breakage between 1.11 and 1.12 in the Compiler Explorer)

The proper way thus is to define such an implementation as soon as the type is introduced. E.g. in 1.70, the standard library got OnceCell stabilized, and the corresponding implementation of From<T> for OnceCell<T> came with the same Rust version, so there’s no potential for breakage.

2 Likes

Thank you for clarifying that the reason the code does not compile is unrelated to the orphan rule, which was the context in which the example was introduced. I'll poke around some other common traits to see if I can find another good example.

I also saw the section in the same chapter about breaking changes, and this example is actually kind of helpful in seeing how semantic versioning would be important, and how far-reaching a blanket impl like impl<T> From<T> for T could be.

This is good clarification for me, as this is more likely how I'd use the generic in practice.

They do violate backwards compatibility when they can get away with it.

Godbolt.

2 Likes