Does From only exist because of the orphan rule?

I was trying to explain to someone today why both From and Into exist, instead of just one or the other.

I can justify using into over from because it plays more nicely with type inference: x.into() is more succinct than _::from(x).

But AFAICT the only reason From exists is because if I want to write a conversion from someone else's Vec2D type to my Vec2D type, I can't impl Into on their type because of the orphan rule (my crate didn't define their type or the std Into trait). Is that the only reason? In a hypothetical Rust 2.0 that didn't have the orphan rule could we just have Into without From?

Answer is, surprisngly enough, in the documentation: Prior to Rust 1.41, if the destination type was not part of the current crate then you couldn’t implement From directly.

Chances are both would still exist, but Into would just be blanked implementaion, with caveat not to implement it directly.

Or maybe it wouldn't exist, it's hard to predict. Note that there are no need for Rust 2.0, documentation tells precisely when two traits have stopped being needed.

Of course when Rust 1.41 arrived there were already tons of crates and removing redundant trait wasn't really possible, but language is ready for that. Today. But not when Rust 1.0 was released.

Sometimes you need to use either From or Into (but not the other, respectively) in bounds due to #20671, I think. Ideally #20671 would not exist, though.

The orphan rule doesn't prevent that. For example, you can implement From<MyType> for i32 even though your crate doesn't define From or i32.

2 Likes

It used to, as pointed out here...

RFC 2451 was the change, if anyone is curious.

So coherence could still be the reason we have both traits, and in fact, it was one of the reasons. (Another was that from constructors were already a convention.)


I'm not seeing the connection offhand, do you have an example? [1]


  1. There are negative tradeoffs to having implied bounds too, but that's another topic. ↩︎

1 Like

I also have difficulties to come up with one, but maybe the following one can demonstrate the issues you can run into.

Consider this:

pub trait TrtA: From<String> {
    type Associated;
}
impl TrtA for String {
    type Associated = ();
}

We could also attempt to use Into here:

pub trait TrtB: Sized
where
    String: Into<Self>,
{
    type Associated;
}
impl TrtB for String {
    type Associated = ();
}

But now using TrtA is easier than using TrtB:

pub struct A<T: TrtA> {
    pub field1: T,
    pub field2: T::Associated,
}

pub struct B<T: TrtB>
where
    String: Into<T>, // we need this
{
    pub field1: T,
    pub field2: T::Associated,
}

Here, struct B requires an extra bound because the compiler can't deduce that T: TrtB implies String: Into<T>.

This gets worse with impl Trait, I believe:

pub fn foo() -> A<impl TrtA> {
    A {
        field1: "Hello".to_string(),
        field2: (),
    }
}

pub fn bar() -> B<impl TrtB> {
    B {
        field1: "Hello".to_string(),
        field2: (),
    }
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0277]: the trait bound `impl TrtB: From<String>` is not satisfied
  --> src/lib.rs:38:17
   |
38 | pub fn bar() -> B<impl TrtB> {
   |                 ^^^^^^^^^^^^ the trait `From<String>` is not implemented for `impl TrtB`
   |
   = note: required for `String` to implement `Into<impl TrtB>`
note: required by a bound in `B`
  --> src/lib.rs:25:13
   |
23 | pub struct B<T: TrtB>
   |            - required by a bound in this struct
24 | where
25 |     String: Into<T>, // we need this
   |             ^^^^^^^ required by this bound in `B`

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

The same issue can happen vice versa, where using From as bound makes the problems (and where I had to use Into instead).

1 Like

Oh, by "need to use in bounds one ... but not the other", you were distinguishing between implied bounds you don't have to spell out and other bounds which you do, depending on if you used From or Into as a supertrait bound or other type of bound. That doesn't have anything to do with why there are two traits AFAIK, though it is an apt observation. (I thought you meant we needed both traits because something was or wasn't possible, vs. ergonomic.)

The RPIT example isn't really worse IMO, you just have to state the bound (like the generic case).

-pub fn bar() -> B<impl TrtB> {
+pub fn bar() -> B<impl TrtB + From<String>> {

How can I do this if From wouldn't exist? (regarding the OP)

But I agree in most cases it's just a matter of ergonomics (and will just require you to add extra bounds).


A similar case might be dyn (instead of impl) where you could run into problems, I think.

1 Like

Ah yes, I missed your point in the playground (more clear after deleting all of TrtA). Thanks for the example!