1

What would be the easiest way to achieve this?

enum E {
    A,
    B,
    C,
}

// Should return "A" for 0, "B" for 1 and "C" for 2
fn convert(i: u32) -> str {
    // ???
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
2080
  • 1,223
  • 1
  • 14
  • 37
  • Related: https://stackoverflow.com/a/32712140/4498831 – Boiethios May 14 '18 at 14:51
  • 1
    It can never return `str` because that hasn't got a size. It would need to be `String` or `&str`. – Peter Hall May 14 '18 at 14:51
  • 1
    *Do not use `transmute` like this!* This is wildly unsafe, and can trivially cause undefined behaviour. Enums in Rust *are not* like enums in C: they **must not** have tag values not defined by the compiler. If you don't completely understand the rules on unsafe coding in Rust, you should not be using `unsafe` code. – DK. May 14 '18 at 14:52
  • @2080 This is going to blow up if that `t` is out of range. You will be much better off to use a `match` statement and handle each case. – Peter Hall May 14 '18 at 14:53
  • 1
    Alternatively, you can have a macro generate your `enum` in the first place, and create a `to_str` method at the same time. – Peter Hall May 14 '18 at 14:54
  • @PeterHall This seems pretty verbose, an assert/range check before the transmute call might be sufficient – 2080 May 14 '18 at 14:55

2 Answers2

3

You cannot return a str, but you can return a &str. Combine ideas from How do I match enum values with an integer? and Get enum as string:

#[macro_use]
extern crate strum_macros;
extern crate strum;

use strum::IntoEnumIterator;

#[derive(EnumIter, AsRefStr)]
enum E {
    A,
    B,
    C,
}

fn main() {
    let e = E::iter().nth(2);
    assert_eq!(e.as_ref().map(|e| e.as_ref()), Some("C"));
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
-3

It was necessary to add the #[derive(Debug)] attribute and define an implementation on the enum:

#[derive(Debug)]
#[derive(Copy, Clone)]
enum E {
    A,
    B,
    C,
}

impl E {
    fn from(t: u8) -> E {
        assert(t<=enum_to_int(E::C), "Enum range check failed!");
        let el: E = unsafe { std::mem::transmute(t) };
        return el;
    }
    fn to_string(&self) -> String {
        return format!("{:?}", self);
    }

    fn string_from_int(t: u8) -> String {
        return E::from(t).to_string();
    }
}


fn enum_to_int(el: &E) -> u8 {
    *el as u8
}

It can then by used like this:

fn main() {
    let s = E::string_from_int(3 as u8);
    println!("{}", s);
}
2080
  • 1,223
  • 1
  • 14
  • 37
  • Why was this downvoted? It works and answers my question. – 2080 May 14 '18 at 15:10
  • 4
    There is no guarantee that the representation of an enum starts at 0. Additionally, you are doing no bounds check. This won't result in a nice error - it may not even cause a panic - it will result in Undefined Behaviour. – Peter Hall May 14 '18 at 15:11
  • 2
    This code causes a segfault when [run in the playground](https://play.rust-lang.org/?gist=9e8991984c79fd37520ff2ae3a9e6786&version=stable&mode=debug). **This is not safe and should not be used**. – Shepmaster May 14 '18 at 15:47
  • 1
    It's worth noting that the segfault is triggered because, as @PeterHall mentioned, if you are out of bounds, the unsafe won't care, and do whatever you told it. That's what trips the segfault: 3 is out-of-bounds, here, the assumed bounds being [0,2]. Since this type of cast is UB for all `#[repr(Rust)]` enums, it's only by 'luck' that it will work when it is in-bound - Rust doesn't make that a guarantee. It would, however, be guaranteed behavior for `#[repr(C)]` C-like enums, but it would be terrible, brittle code, and bad, awful practice. A `match` is the way to go, or @Shepmaster's answer. – Zarenor May 14 '18 at 17:54
  • 1
    Adding `#[repr(u8)]` to the enum, and changing `3` to `2` so the `transmute` call won't become out of bounds, is the minimum you need to make this answer correct. Making it *safe* is another question, but you could do that with range checking in the `from` function. – trent May 14 '18 at 19:30