How to deref such kinds of enum?


enum MyEnum{
    f(f64),
    i(i64),
    s(String)
}

impl std::ops::Deref for MyEnum {
    // type Target = ???? -> only one type is allowed here.; but MyEnum contains three kinds of types: f64, i64, String
    fn deref(&self) -> &Self::Target {
        
    }
}

fn main(){
    let a = MyEnum::f(1.1);
    let b = MyEnum::i(100);
    let c = MyEnum::s(String::from("hello world"));
    // println!("{}", *a);   =>print out: 1.1
    // println!("{}", *b);   =>print out: 100
    // println!("{}", *c);   =>print out: hello world
}


I wannt to deref the enum (MyEnum), but the fn deref cannot implemented for three different kinds of value,
what the best practice for this situation?

Normally, you'll reach for Deref if your type is intended as a transparent wrapper around some other type. It's not possible to implement Deref with three output types which can only be determined at runtime because Rust is a statically typed language and the type of everything (including the returned value) needs to be known at compile time.

Typically you'd do that sort of "this method can return a f64 or a i64 or a String" by returning an enum... which would just put us back where we started :sweat_smile:

In your case, it feels like you want some sort of getter that lets you view the value as a certain type. Maybe something like this?

enum MyEnum{
    f(f64),
    i(i64),
    s(String)
}

impl MyEnum {
  fn as_f(&self) -> Option<f64> {
    match self {
      MyEnum::f(f) => Some(f),
      _ => None,
    }
  }

  fn as_i(&self) -> Option<i64> { ... }
  fn as_s(&self) -> Option<&str> { ... }
}

Alternatively, if you just want to be able to print the MyEnum using println!(), you can implement std::fmt::Display so it delegates to the internal value.

use std::fmt::{self, Formatter, Display};

impl Display for MyEnum {
  fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
    match self {
      MyEnum::f(float) => write!(f, "{float}"),
      MyEnum::i(int) => write!(f, "{int}"),
      MyEnum::String(s) => write!(f, "{s}"),
    }
  }
}
4 Likes

Rust is strictly typed, and enum variants aren't unique types. So you can't have a method that returns different types (&f64, &i64, &str) based on which enum variant a variable currently is. This means your main example can't work quite they way you've spelled it (even with something besides Deref) -- context can guide trait selection so you can return different types form a single method, but it won't be based on which variant you are. So if this is going to be a single method that returns different types, you'll need some context at the call site so the compiler knows what output type you're expecting.

Alternatively, you can make dedicated methods like as_f64.

Note that each method that returns a type like &f64 might fail. That is, the method that returns &f64 will have to be able to handle being called when the variant is i or s, and you can't obtain a &f64 from those. This is true whether or not you go with a trait or different methods. You could panic or return a dummy value, but a more idiomatic approach would be to return an Option or a Result. (If callers want to panic or use a default value, they can do so at the call site.)

If we were talking about fields in a struct, the answer would be AsRef<_>. Like the advice in that documentation says, these types of conversions are expected to be infallible; they recommend dedicated methods for this use case. Another alternative would be to implement TryFrom.

Playground.


Some more alternatives:

  • Implement the functionality you want (e.g. Display) on MyEnum directly
  • Return an enum
  • Return a type-erased type, e.g.
    • &dyn Display
    • impl Display + '_
6 Likes

After struggling with Deref, Borrow, and AsRef myself for a while, I would be interested in why you think (or thought) that Deref is needed (or for what purpose you would like to implement Deref).

Perhaps it's not needed in your case, and given your example (not knowing what you want to do later), I would likely write it as follows:

use std::fmt;

enum MyEnum{
    F(f64),
    I(i64),
    S(String)
}

impl fmt::Display for MyEnum {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            MyEnum::F(x) => write!(f, "{x}"),
            MyEnum::I(x) => write!(f, "{x}"),
            MyEnum::S(x) => f.write_str(x),
        }
    }
}

fn main(){
    let a = MyEnum::F(1.1);
    let b = MyEnum::I(100);
    let c = MyEnum::S(String::from("hello world"));
    println!("{}", a);
    println!("{}", b);
    println!("{}", c);
}

(Playground)

Now that is if you want to have the string representation for printing it somewhere. Implementing std::fmt::Display automatically makes a type implementing ToString (thus also allowing you to use .to_string() if you like.

Another option would be to use From, possibly in combination with Cow:

use std::borrow::Cow;

enum MyEnum{
    F(f64),
    I(i64),
    S(String)
}

impl From<MyEnum> for String {
    fn from(value: MyEnum) -> String {
        match value {
            MyEnum::F(x) => x.to_string(),
            MyEnum::I(x) => x.to_string(),
            MyEnum::S(x) => x,
        }
    }
}

impl<'a> From<&'a MyEnum> for Cow<'a, str> {
    fn from(value: &'a MyEnum) -> Cow<'a, str> {
        match value {
            MyEnum::F(x) => Cow::Owned(x.to_string()),
            MyEnum::I(x) => Cow::Owned(x.to_string()),
            MyEnum::S(x) => Cow::Borrowed(&x),
        }
    }
}

fn main(){
    let a = MyEnum::F(1.1);
    let b = MyEnum::I(100);
    let c = MyEnum::S(String::from("hello world"));
    // These work on references:
    println!("{}", Cow::<str>::from(&a));
    println!("{}", Cow::<str>::from(&b));
    println!("{}", Cow::<str>::from(&c));
    // These will consume the corresponding value:
    println!("{}", String::from(a));
    println!("{}", String::from(b));
    println!("{}", String::from(c));
}

(Playground)

The difficulty is that you cannot return a reference to a str slice when the enum value is a float or integer (because you will have to create a String and store it somewhere in order to be able to reference it). It's only possible to return a reference to a str if the enum is MyEnum::S. And Deref must never fail. Using AsRef has the same problem, and both should not involve complex operations but be "cheap".

So the only alternative seems to be From (and Into, which is related, but you should ideally implement From). By allowing conversion from a reference and returning a Cow, you can avoid unnecessary cloning in case the enum was MyEnum::S.

Note: Cow<'a, str> already implements Deref<Target=str> for you.

But I believe what's best really depends on your use case / scenario.


You could also use a method that returns the Cow without involving From and Into at all:

use std::borrow::Cow;

enum MyEnum{
    F(f64),
    I(i64),
    S(String)
}

impl MyEnum {
    fn cow_str(&self) -> Cow<'_, str> {
        match self {
            MyEnum::F(x) => Cow::Owned(x.to_string()),
            MyEnum::I(x) => Cow::Owned(x.to_string()),
            MyEnum::S(x) => Cow::Borrowed(&x),
        }
    }
}

fn main(){
    let a = MyEnum::F(1.1);
    let b = MyEnum::I(100);
    let c = MyEnum::S(String::from("hello world"));
    println!("{}", a.cow_str());
    println!("{}", b.cow_str());
    println!("{}", c.cow_str());
}

(Playground)


Oh, and speaking of different use cases:
If you're just interested in Debug output, you could also simply do:

#[derive(Debug)]
enum MyEnum{
    F(f64),
    I(i64),
    S(String)
}

fn main(){
    let a = MyEnum::F(1.1);
    let b = MyEnum::I(100);
    let c = MyEnum::S(String::from("hello world"));
    println!("{:?}", a);
    println!("{:?}", b);
    println!("{:?}", c);
}

(Playground)

Note the {:?} in the formatting string.

2 Likes

I would like to add that using Display, ToString, and creating a String or a Cow will do different things under the hood (please correct me if I'm wrong).

AFAIK Display does not depend on std, that means it doesn't need to allocate memory on the heap. Thus if you use it to print out a value, it doesn't need to allocate a String first (it could send the output directly to the I/O interface).

.to_string(), which is defined in the ToString trait that gets automatically implemented when you implement Display, will allow you to use the implementation of Display to create an allocated String nonetheless.

Using Into<String> will always create a new String buffer on the heap (and consume its input).

But even the Cow proposal I made would possibly unnecessarily allocate a String, because when converting a MyEnum::F or MyEnum::I into the Cow, it will need to create an own String for the result. When you just want to print the value, this isn't necessary (you could write it directly to the I/O interface).

I believe the usual case will be to just implement Display and possibly use .to_string() where needed. But if you really only need a Dereferenceable value (for whichever reason?), then Cow might be the lightest option (in theory).

1 Like