What's up with the Orphan Rule? impl From<MyType> for Vec<u8>

Hi,

I just realized that the Rust compiler (currently 1.39) accepts the following code:

    struct MyMagicType;
    impl From<MyMagicType> for Vec<u8> {
    	fn from(_: MyMagicType) -> Vec<u8> {
    		Vec::new()
    	}
    }

playground

This is great, but I don't get why it works given the Orphan Rule. Indeed, both From and Vec are defined externally.

The book says

But we can't implement external traits on external types. For example, we can't implement the Display trait on Vec within our aggregator crate, because Display and Vec are defined in the standard library and aren't local to our aggregator crate. This restriction is part of a property of programs called coherence, and more specifically the orphan rule, so named because the parent type is not present. This rule ensures that other people's code can't break your code and vice versa. Without the rule, two crates could implement the same trait for the same type, and Rust wouldn't know which implementation to use.

The official Rust documentation of the From trait says:

Only implement Into if a conversion to a type outside the current crate is required. From cannot do these type of conversions because of Rust's orphaning rules.

But as you can see there is no need anymore to implement Into in order to convert into an external type if I can just implement From on it like I did above.

I tried looking for an update in the changelog about that but couldn't find anything. Is there someone who can explains this?

Thanks!

Edit: It looks like it works since Rust 1.7.0, but there is no details in the announcement (Announcing Rust 1.7 | Rust Blog).

1 Like

MyMagicType is, however, not.

You can think of it like this: a generic type parameter defines a type-level function. For example, a generic type is not really a type, it is only a schema for a type, and supplying a type as its type argument results in a type, e.g. Vec is not a type, Vec<u8> is. Similarly, it can be said that From is not a trait, only From<MyMagicType> is. And From<MyMagicType> can only exist if MyMagicType exists, i.e. it also "comes from the current crate" in a sense.

Now, if the implementation of a trait is only restricted when neither the trait nor the implementing type comes from the current crate, then there's no way a 3rd-party crate could sneakily implement From<MyMagicType> for Vec<u8>, but you can still be (and are) allowed to.

(Disclaimer: this is all high-level handwaving for the sake of basic intuition. I have to admit that I'm not familiar with the exact implementation details of coherence checking in the compiler.)

4 Likes

Nice!
But now I'm having trouble imagining a case where I can't implement From and need to implement Into. You would implement Into on LocalType. But you can implement From on ExternalType anyway. I'm pretty sure I had an issue with this in the past, but can't remember how.

To be honest, I have never encountered such a case. (I have, however, seen numerous examples of programmers implementing Into but not From for no good reason, which made me sad.)

Haha. a good rule of thumb is to always try to implement From first.
No matter how hard I try I can't find an example where From is rejected and Into isn't right now.

struct Wrapper<T>(Vec<T>);

// forbidden
impl<T> From<Wrapper<T>> for Vec<T> {
    fn from(Wrapper(value): Wrapper<T>) -> Vec<T> { value }
}

// allowed
impl<T> Into<Vec<T>> for Wrapper<T> {
    fn into(self) -> Vec<T> { self.0 }
}
9 Likes

Oh, the good old "add one more level of indirection" trick. Nice.

1 Like

That will change soon (or better it is already changed on nightly) due to the re-rebalance coherence RFC implementation which allows the first impl. (Having both will continue to result in a conflicting implementations error obviously)

3 Likes

Since impl From<MyMagicType> for Vec<u8> is allowed, this also means that it would be a breaking change for the alloc crate to add a new impl<T> From<T> for Vec<u8> that would collide with T = MyMagicType.

That example would already collide with impl<T> From<T> for T, but there may be some constraint on T that could escape this. The point stands as a consideration for general API evolution though.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.