0

Problem

I want to write a single function that allows me to convert from type A (in this case, u8) to type B (a custom type), and then back from type B to type A. According to the first paragraph of the entry about the traits From and Into of Rust by Example:

The From and Into traits are inherently linked, and this is actually part of its implementation. If you are able to convert type A from type B, then it should be easy to believe that we should be able to convert type B to type A.

However, implementing From (i.e.impl From<A> for B) allowed me to convert from A to B and only A to B, except in two different ways (still not sure why two ways are necessary but anyway). Can I convert from B to A using the same implementation? Or is there no way to use the information already there?

What I have tried

I tried implementing From (or TryFrom in this case) on my type NormalMneumonic like so

impl TryFrom<&str> for NormalMneumonic {
    type Error = Error;
    fn try_from(value: &str) -> Result<Self, Self::Error> {
        match value {
            "JP" => Ok(Self::Jump),
            // --snip--
            _ => Err("Input is not a normal menumonic."),
        }
    }
}

With that I'm able to do

let mneumonic_1 /*: Result<NormalMneumonic, _>*/ = NormalMneumonic::try_from("JP");
let mneumonic_2: Result<NormalMneumonic, _> = "JP".try_into();
assert_eq!(mneumonic_1, mneumonic_2);

but I haven't found a way to convert, in this case, from NormalMneumonic back into &str. I'm looking for something like

let mneumonic_string = NormalMneumonic::Jump.try_into(); // or perhaps &str::try_from(NormalMneumonic::Jump)

Some context

I'm trying to write an assembler and a linker for a simplified assembly language using Rust. One of the data types I have defined to help with that is NormalMneumonic, which is just an enum with a variant for each valid mneumonic.

On writing the assembler, I'll need to read some text file and write some binary, and on linking I'll need to read back some binary files and write a different binary file. With that in mind, I was looking for a way to convert back and forth between a string slice (&str) or a byte (u8) and a NormalMneumonic variant.

From the quote I mentioned, I thought converting back and forth between types was the use case for the From trait, but it seems the book in this case just uses misleading language.

2 Answers2

3

No, a From<A> for B implementation will not create a From<B> for A implementation.

The From trait is composed of only a single method fn from(a: A) -> B. With just this signature, would you be able to create the reverse implementation for all A and B? Of course not! And the compiler will not look at the existing implementation's body to try to deduce the other. For one thing, many conversions are lossy, many conversions are fallible, and may have hurdles converting one way that don't exist when converting the opposite direction. So even if the compiler did look at the existing implementation, its not practical or even possible in general.

From the quote I mentioned, I thought converting back and forth between types was the use case for the From trait, but it seems the book in this case just uses misleading language.

Indeed, you've misinterpreted the quote. It is essentially saying the same thing twice, but in a different context: "convert type A from type B" is the same operation as "convert type B to type A", both are B -> A, just the subject of the phrasing has changed. And this reflects the only difference between From and Into. The syntaxes A::from(b) and b.into() (with inferred A) cannot be done with a single trait.


If you're looking to make your life easier when dealing with enums, as already mentioned, the strum crate has many derive macros designed to:

See these existing answers for other options:

kmdreko
  • 42,554
  • 6
  • 57
  • 106
  • I indeed was a bit too naive to consider `From` would allow lossy conversion but also infer `B -> A` from `A -> B`. It would be interesting to have something like that though, and theoretically possible. Thanks for the suggestions! – tomaz-suller Dec 29 '22 at 01:16
  • For the record, converting to and from `u8` or other numerical type can be done using [`num_enum`](https://crates.io/crates/num_enum) and deriving from `TryFromPrimitive` and `IntoPrimitive`. There's a comment on a Rust RFC mentioning crates as the way to go for this use case. – tomaz-suller Dec 30 '22 at 14:24
  • @tomaz-suller Got a link to the RFC? There are many crates for using enums like this, so I'm curious if the Rust team is considering one of them better than the others. – kmdreko Dec 30 '22 at 15:19
  • I was looking for an answer on the GitHub app on my phone, and I couldn't find it on my history. Sorry :-/ – tomaz-suller Dec 30 '22 at 17:35
0

Maybe strum_macros can help.

It can generate code to convert enum value to &str

use strum_macros::IntoStaticStr;

#[derive(IntoStaticStr)]
enum NormalMneumonic {
}
Red Forks
  • 1
  • 2
  • Thanks for the suggestion. That would indeed let me associate each variant with a `&str`, but it seems not with a `u8` at the same time (as I was hoping to do), from the context I gave on the question – tomaz-suller Dec 28 '22 at 16:39
  • I would also have to write code to convert from `&str` into my enum type, which would mean two places in my code saying the same thing, which is exactly what I wanted to avoid – tomaz-suller Dec 28 '22 at 16:40