An innocent eye to From/Into

The subject is in the last comment

pub enum ResistorColor {
    Red,
    Black,
    Brown,
}

impl TryFrom<u32> for ResistorColor {
    type Error = String;

    fn try_from(value: u32) -> Result<Self, Self::Error> {
        let description = match value {
            0 => "Black",
            1 => "Brown",
            2 => "Red",
            _ => return Err("Out of range".to_owned()),
        };

        Self::try_from(description)
    }
}

impl TryFrom<&str> for ResistorColor {
    type Error = String;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        Ok(match value {
            "Black" => ResistorColor::Black,
            "Brown" => ResistorColor::Brown,
            "Red" => ResistorColor::Red,
            _ => return Err("Not valid".to_owned()),
        })
    }
}

fn main() {
    /* that is implemented already */
    assert_eq!(Ok(ResistorColor::Brown), ResistorColor::try_from(1));
    assert_eq!(Ok(ResistorColor::Brown), ResistorColor::try_from("Brown"));

    /* that is my naive expectation of Into concept */
    assert_eq!(Ok(1), ResistorColor::try_into::<u32>(ResistorColor::Brown));
    assert_eq!(
        Ok("Brown"),
        ResistorColor::try_into::<&str>(ResistorColor::Brown)
    );
}

Is there a question? Your code compiles and runs if you fix the numerous syntax errors.

try_into is automatically implemented if you implement From or TryFrom for the foreign type:

impl From<ResistorColor> for u32 {
    fn from(value: ResistorColor) -> Self {
        match value {
            ResistorColor::Black => 0,
            ResistorColor::Brown => 1,
            ResistorColor::Red => 2,
        }
    }
}

Then you can do:

+#[derive(PartialEq, Eq, Debug)]
pub enum ResistorColor {
    /* … */
}
/* … */
-    assert_eq!(Ok(1), ResistorColor::try_into::<u32>(ResistorColor::Brown));
+    assert_eq!(Ok(1), ResistorColor::try_into(ResistorColor::Brown));
+    assert_eq!(Ok(1), TryInto::<u32>::try_into(ResistorColor::Brown));
+    assert_eq!(Ok(1), u32::try_from(ResistorColor::Brown));

(Playground)

Note that the derive is only needed for assert_eq! to work properly.

Also note that try_into has no type argument (the type argument is part of the TryInto trait). So you cannot use try_into::<SomeType>(…). Instead I would use SomeType::try_from(…).

1 Like

This looks like you've expecting the compiler to generate impl TryInto<T> for U where you have impl TryFrom<T> for U. But this is impossible in general - trait implementation can be arbitrary complex, it can't be reversed automatically.

4 Likes

Here is rather proposal :wink:

What is From: get Brown from 1
What is Into: from 1 get Brown

What I would like from Into: get 1 from Brown

It is almost I want :+1:

But if I remove logically (I hope) redundant From implementation there is an error:

error[E0277]: the trait bound {integer}: From<ResistorColor> is not satisfied

and I know that bug or feature request is old enough

But it is possible, I suppose. Not just to change places like "get Brown from 1" and "from 1 get Brown" :slightly_smiling_face:

I think that bug / feature request only refers to the wording of the error message (complaining that it proposes to implement From, while it would be sufficient to implement TryFrom)

Generally, I would agree to what @Cerber-Ursi said:

1 Like

This would be impl Into<u32> for ResistorColor. How could it be automatically created from impl From<u32> for ResistorColor, in your opinion?

1 Like

We have types affected - u32 and ResistorColor, have matching rules, have "try" in description - so we can try :slightly_smiling_face:

I don't know how exactly it can be done. Have heard only about vtables

That's not correct. The blanket Into (and TryInto) impl doesn't (and can't) perform the inverse of the From (or TryFrom) it refers to. In other words, Into<U> for T is implemented if U: From<T>, an markedly not if T: From<U>.

Into is merely meant to work as syntactic sugar in contexts where a method call looks prettier than spelling out the full UFCS syntax, i.e. something.into() rather than OtherThing::from(something). As others have already pointed out, it's not possible to automatically find an inverse of these conversions in general.

1 Like

yes, you are right. I used common language in my description of what is going on

The question is, what we can try to do? There're no "matching rules" in general case, and Rust will not analyze the function bodies anyway (since this approach will be too fragile).

1 Like

If you don't know that then it shouldn't be done.

Language features of the form “I have no idea how that works, but it works” are always a problem. They may look nifty in the short-term, but medium term they become a liability.

Because if you don't know how something works then you don't have any idea when it wouldn't work. Or work in a strange way.

That's how PHP ended up with an equality operator which boldly proclaims that strings "1e3" and "1000" are equal.

2 Likes

in Rust coercion also exists

assert_eq!("aaa", "aaa".to_owned());

it is equal, but not the same (use === in PHP instead)

This is not a coercion but operator overloading instead, because there's an impl PartialEq<String> for &str.

It's true that Rust has coercions though, but they're completly different than what's happening in this example Type coercions - The Rust Reference

3 Likes

Sure, but I'm not talking about details.

I'm just saying that if you compare two strings (e.g. two inventory ids) you wouldn't expect "1e3" and "1000" to be considered equal.

And sure, I can use === in Javascript or PHP to get predictably-working program but this just doesn't make much sense: why have some feature in a language if the only thing you need to know about it is to never use it?

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.