Zannick
Zannick

Reputation: 25

Why does Default derived on an enum not apply to references to the enum?

I have an enum that has Default derived on it, say for example:

#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum Enum<T> {
    #[default] None,
    One(T),
    Two(T),
}

Vec has methods like last() which return an Option<&T>. But when I call unwrap_or_default() on the Option<&T>, I get the following error:

22 |     println!("{:?}", v.last().unwrap_or_default());
   |                      ^^^^^^^^ ----------------- required by a bound introduced by this call
   |                      |
   |                      the trait `Default` is not implemented for `&Enum<i8>`
   |
   = help: the trait `Default` is implemented for `Enum<T>`
note: required by a bound in `Option::<T>::unwrap_or_default`

I can work around this in one of two ways:

  1. Use unwrap_or() and provide a reference to the default element manually:
    v.last().unwrap_or(&Enum::None)
    
  2. Implement Default for &Enum.
    impl<'a> Default for &'a Enum2 {
        fn default() -> &'a Enum2 { &Enum2::None }
    }
    

But I don't realize why I should have to. Is there a separate trait I should derive to use the tagged default element correctly here?

Playground


Context: the goal was to output "None" if the list was empty and Enum::Display(val) from Some(val) for the last element, within a sequence of format arguments.

match self.history.last() { None => "None", Some(val) => val }

is illegal: the types of the match arms must match. Many other options don't work due to val being borrowed and self not being Copy. Defining a separate default for Enum was an easily-thought-of option to avoid extra string formatting/allocation.

Upvotes: 0

Views: 1484

Answers (1)

Finomnis
Finomnis

Reputation: 22748

What should Default return for &Enum?

Remember, a reference must point to some valid data, and does not keep said data alive.

So even if the Default implementation would allocate a new object, there is nowhere to keep it alive, because the returning reference wouldn't keep it alive. The only way is to store the default element somewhere globally; and then it would be shared for everyone. Many objects are more complicated and cannot be simply stored globally, because they are not const-initializable. What should happen then?

The fact that a reference does not implement Default automatically is intentional and with good reasons. unwrap_or_default is simply the wrong function to call if your contained value is a reference. Use match or if let to extract the value without having to create a fallback value. unwrap_or is also a valid choice if you must absolutely have a fallback value, although I don't see how this is easily possible to implement with a reference without falling back to global const values. Which is also fine, if it fits your usecase.


In your case, it's of course fine to derive Default manually and is probably what I would do if I absolutely had to. But to me, requiring this in the first place is most likely a code smell in the first place.

For enums that are that simple, it's actually an anti-pattern to use a non-mutable reference instead of a value. References are of size 64 in a 64bit system, while an enum of with three simple values is most likely a 8 bits large. So using a reference is an overhead.

So in this case, you can use copied() to query the object by-value. This is most likely even faster than querying it by reference.

#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum Enum<T> {
    #[default]
    None,
    One(T),
    Two(T),
}

fn main() {
    let v: Vec<Enum<u32>> = vec![];

    println!("{:?}", v.last().copied().unwrap_or_default());
}
None

Either way, I think the fact that you have this question in the first place indicates that there is an architectural issue in your code.

There are two cases:

  • Your enum members all contain valid data; in that case it is always important to distinguish in .last() between "one element with some data" and "no element", and falling back to a default wouldn't be viable.
  • Your enum has a None member, in which case having the None element at .last() would be identical to having no members. (which is what I suspect your code does). In that case it's pointless to manually define a None member, though, simply use the Option enum that already exists. That would make your problem trivially solvable; Option already implements Default (which is None) and can propagate the reference to its member via .as_ref():
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum EnumContent<T> {
    One(T),
    Two(T),
}

// Option already implements `Default`.
pub type Enum<T> = Option<EnumContent<T>>;

fn main() {
    let mut v: Vec<Enum<u32>> = vec![];
    println!("{:?} -> {:?}", v, v.last().and_then(Option::as_ref));

    v.push(Some(EnumContent::One(42)));
    println!("{:?} -> {:?}", v, v.last().and_then(Option::as_ref));

    v.push(None);
    println!("{:?} -> {:?}", v, v.last().and_then(Option::as_ref));
}
[] -> None
[Some(One(42))] -> Some(One(42))
[Some(One(42)), None] -> None

Upvotes: 2

Related Questions