How to use a generic enum, with a default type, without having to specify the generic type

So I have an enum definition that looks like this

#[derive(Debug)]
enum Greeting<T = String> {
    Hi(T),
    Bye
}

I can use the Hi variant like this

fn main() {
    println!("{:?}", Greeting::Hi("How you doing".to_string()));
}

But if I try to use the Bye variant like this

fn main() {
    println!("{:?}", Greeting::Bye);
}

I get the error

   Compiling playground v0.0.1 (/playground)
error[E0282]: type annotations needed
  --> src/main.rs:10:22
   |
10 |     println!("{:?}", Greeting::Bye);
   |                      ^^^^^^^^^^^^^ cannot infer type of the type parameter `T` declared on the enum `Greeting`
   |
help: consider specifying the generic argument
   |
10 |     println!("{:?}", Greeting::<T>::Bye);
   |                              +++++

For more information about this error, try `rustc --explain E0282`.
error: could not compile `playground` (bin "playground") due to previous error

And to make it work, I would have to use it like this

fn main() {
    println!("{:?}", Greeting::<String>::Bye);
}

Having to manually specify the ::<String>:: part makes me sad :frowning:

Is there anyway I can make this work, without specifying the ::<String>:: part? I was of the opinion that specifying the default to String in the definition, like this enum Greeting<T = String> should have helped...but that apparently is not the case..

Is there anyway I can achieve this?

I don't think there is a way to achieve this with the enum as it is, since the Debug implementation could depend on the generic type.

Edit: the implementation does actually depend on the generic type since the debug function is defined on the type Greetings, not on the individual variants Bye and Hi.

Type defaults are considered when completely elided in code location that expects a type, but inference ignores type defaults in expressions. Here's a summary.

So, this works:

println!("{:?}", <Greeting>::Bye);
2 Likes

So basically no way to do want I want? Have a enum with a generic type that can be used with one of it's variants, without the verbosity of having to explicitly setting the type?

The example shows how to do that using a default type parameter.

If you mean is there a way to do it without the default type parameter / is there a way to have a variant around with the generic of the enum "unset", the answer is no. Rust is statically typed and the type of a variant is the type of the enum. The type parameter must be specified or inferred in some way.

You could have two different enums and a From implementation or such.

1 Like

Such thing doesn't exist in Rust. Generic types don't exist in runtime and generic enums are not an exception.

What does exist is series of types with different Bye representations and they are, well, different types.

You may ask compiler to pick the best one for you (and <Greeting>::Bye does precisely that), but you can not use something that doesn't exist.

If you have a generic type and don't care about what the generic part actually is for whatever reason, there will eventually be the never (!) type. But in the meantime, you can build something similar with an enum with no options, like:

#[derive(Debug)]
enum Never {}

so your Option-like Greeting enum could be instantiated with:

let bye: Greeting<Never> = Greeting::Bye;

(and that's the only way to instantiate that particular Greeting, since there's no way to build the Hi option).

There's a very concrete reason why it's not possible: in your case the size of Greeting<T> is the size of T + one byte for the variant - excepted if T has a niche then Greeting<T>'s size will be the same as T's.

So the compiler wouldn't be able to compute the size of Greeting::Bye without knowing what a T is. And you can't put something on the stack if you don't know its size at compile-time (alloca() be damned).

1 Like

Just an idea because no one suggested it.
If your question is about ergonomics of using the type when you don't care about what T is, you could use a type alias:

type Greetings = Greeting<String>;

fn main() {
    let hello = Greetings::Hi("hi".into());
    let bye = Greetings::Bye;
}

Playground

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.