From & Into confusion - Why do we need both?

This question has probably been asked countless of times, but I've been searching alot and just can't find a satisfying answer so let's give it another try :slight_smile:

Reading the documentation about the Into trait it says:

The Into trait is simply the reciprocal of the From trait.

I understood that as the Into beeing the inverse functionality of From, in other words, that it would allow me to convert A from B and B into A but when I experiment it seems I can only do A from B and B into A which semantically is the same thing - both takes B and returns A.

Example:

struct A();
struct B();

impl From<B> for A {
    fn from(b: B) -> Self {
        Self()
    }
}

fn test(b: B) {
    /*
    Both of these will return type A - How can I instead convert A back into B?
    */
    let b_into_a: A = b.into();
    let a_from_b = A::from(b);
}

Here are my questions:

  • Is there a way to convert A => B without any further implementations or do I also need to implement Frym<A> for B?
  • What's the use of having two ways of converting A => B?
1 Like

Into only exists for syntactic convenience.

No, there isn't. Why should the inverse of a conversion be automatic? Two different types usually aren't able to express the same set of values, so if one is convertible to another, that direction may lose information, so the other direction will likely not be possible (or it will be fallible, etc.).

Besides, there's simply no way technically for the compiler to tell what exactly should happen in such a conversion just by looking at the implementation of its inverse.

2 Likes

Have you tried to read the documentation?

Two traits mostly exist because of old orphan rules. If Rust would have been implemented today, without any backward compatibility considerations, then there would have been no need to have two traits.

5 Likes

You must implement it, there's no way to know how what that would do, if it's even possible. For example there's an implementation of From<u32> for u64, but it's not always possible to go from u64 to u32. Or maybe multiple values of B when converted to a value of A given the same value, then what should be returned when you convert that value of A back to a value of B?

AFAIK originally if A and B were defined in two different crates, then A's crate could implement only From<B> for A, while B's crate could implement only Into<A> for B. This such traits to be implemented no matter which is the depending and dependent crate.

Another reason is also because it allows users to do the conversion both in method form with .into() and by specifying the type to convert into with A::from(b). There are cases when one is more pleasing than the other, so being able to use both is nice.

2 Likes

Thanks you all for giving me a clear and unambiguous answer - Now I don't need to search for things that does not exist or worry about if there's a better way :slight_smile: peace at last

The change that allowed implementing From in more cases that the documentation talks about is due to RFC 2451.

You could always write

impl From<B> for A { /* ... */ }

for concrete types (without conflicts), but before 1.41 you couldn't write a generic implementation

impl<T> From<MyType<T>> for Vec<T> { /* ... */ }

because the parameter T appears "before" the local MyType<_>. With RFC 2451, it's allowed because the T is "covered" by the Vec.


If you read the current orphan rules carefully, you might note that you still can't write these:

impl<T> From<MyType<T>> for T { /* ... */ }
impl<T> From<MyType<T>> for Box<T> { /* ... */ }
impl<'a, T> From<MyType<T>> for &'a T { /* ... */ } // or &'a mut

but in this particular case, you can't write these either:

impl<T> Into<T> for MyType<T> { /* ... */ }
impl<T> Into<Box<T>> for MyType<T> { /* ... */ }
impl<'a, T> Into<&'a T> for MyType<T> { /* ... */ }

because it conflicts with the existing blanket implementation:

impl<T, U> Into<U> for T where U: From<T> { /* ... */ }

either directly (due to an existing From implementation in std), or because downstream creates can implement

impl<'a> From<MyType<DownstreamType>> for &'a DownstreamType { /* ... */ }
// results in `Into<&'_ DownstreamType> for MyType<DownstreamType>`

so there need not be an asterisk on the advice to implement From.

2 Likes

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.