为什么枚举上的默认派生不适用于对枚举的引用?

huangapple go评论54阅读模式
英文:

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

问题

我有一个enum,在它上面派生了`Default`,举个例子:

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

Vec有一些方法,比如last(),它返回一个Option<&T>。但是当我在Option<&T>上调用unwrap_or_default()时,我会得到以下错误:

22 |     println!("{:?}", v.last().unwrap_or_default());
   |                      ^^^^^^^^ ----------------- 由这个调用引入的约束
   |                      |
   |                      trait `Default` 未实现于 `&Enum<i8>`
   |
   = 帮助: trait `Default` 已为 `Enum<T>` 实现
note:  `Option::<T>::unwrap_or_default` 中需要的约束

我可以通过以下两种方式解决这个问题:

  1. 使用unwrap_or()并手动提供默认元素的引用:
    v.last().unwrap_or(&Enum::None)
    
  2. &Enum实现Default
    impl<'a> Default for &'a Enum2 {
        fn default() -> &'a Enum2 { &Enum2::None }
    }
    

但我不明白为什么我必须这样做。在这里,是否有一个单独的trait我应该派生,以正确使用标记的默认元素?

Playground


背景信息:目标是在列表为空时输出"None",并在format参数序列中的最后一个元素中从Some(val)输出Enum::Display(val)

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

是不合法的:match arms的类型必须匹配。由于val被借用并且self不是Copy,许多其他选项不起作用。为Enum定义一个单独的默认值是一个容易想到的选项,以避免额外的字符串格式化/分配。


<details>
<summary>英文:</summary>

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

```rs
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum Enum&lt;T&gt; {
    #[default] None,
    One(T),
    Two(T),
}

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

22 |     println!(&quot;{:?}&quot;, v.last().unwrap_or_default());
   |                      ^^^^^^^^ ----------------- required by a bound introduced by this call
   |                      |
   |                      the trait `Default` is not implemented for `&amp;Enum&lt;i8&gt;`
   |
   = help: the trait `Default` is implemented for `Enum&lt;T&gt;`
note: required by a bound in `Option::&lt;T&gt;::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(&amp;Enum::None)
    
  2. Implement Default for &amp;Enum.
    impl&lt;&#39;a&gt; Default for &amp;&#39;a Enum2 {
        fn default() -&gt; &amp;&#39;a Enum2 { &amp;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 =&gt; &quot;None&quot;, Some(val) =&gt; 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.

答案1

得分: 2

以下是您要的翻译内容:

What should Default return for &amp;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.

在您的情况下,如果必须的话,手动派生 Default 当然是可以的,这可能是我会做的。但对我来说,首先需要这样做很可能是代码中存在问题的迹象。

对于如此简单的枚举,使用非可变引用而不是值实际上是一种反模式。在 64 位系统中,引用的大小为 64,而包含三个简单值的枚举很可能只有 8 位大。因此,使用引用会增加开销。

因此,在这种情况下,您可以使用 copied() 通过值查询对象。这很可能比通过引用查询对象要快。

#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum Enum&lt;T&gt; {
    #[default]
    None,
    One(T),
    Two(T),
}

fn main() {
    let v: Vec&lt;Enum&lt;u32&gt;&gt; = vec![];

    println!(&quot;{:?}&quot;, v.last().copied().unwrap_or_default());
}

None

无论如何,我认为您之所以首先提出这个问题,说明您的代码存在架构问题。

有两种情况:

  • 您的枚举成员都包含有效数据;在这种情况下,在 .last() 中始终重要的是区分 "包含一些数据的一个元素" 和 "没有元素",并且退回到默认值可能不可行。
  • 您的枚举有一个 None 成员,在这种情况下,将 None 元素放在 .last() 上等同于没有成员(这是我怀疑您的代码所做的)。在这种情况下,手动定义 None 成员是毫无意义的,只需使用已经存在的 Option 枚举。这将使您的问题变得容易解决;Option 已经实现了 Default(即 None),并可以通过 .as_ref() 传播引用到其成员:
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum EnumContent&lt;T&gt; {
    One(T),
    Two(T),
}

// Option 已经实现了 `Default`。
pub type Enum&lt;T&gt; = Option&lt;EnumContent&lt;T&gt;&gt;;

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

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

    v.push(None);
    println!(&quot;{:?} -&gt; {:?}&quot;, v, v.last().and_then(Option::as_ref));
}
[] -&gt; None
[Some(One(42))] -&gt; Some(One(42))
[Some(One(42)), None] -&gt; None
英文:

What should Default return for &amp;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&lt;T&gt; {
    #[default]
    None,
    One(T),
    Two(T),
}

fn main() {
    let v: Vec&lt;Enum&lt;u32&gt;&gt; = vec![];

    println!(&quot;{:?}&quot;, 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&lt;T&gt; {
    One(T),
    Two(T),
}

// Option already implements `Default`.
pub type Enum&lt;T&gt; = Option&lt;EnumContent&lt;T&gt;&gt;;

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

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

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

huangapple
  • 本文由 发表于 2023年2月19日 02:42:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/75495572.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定