英文:
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` 中需要的约束
我可以通过以下两种方式解决这个问题:
- 使用
unwrap_or()
并手动提供默认元素的引用:v.last().unwrap_or(&Enum::None)
- 为
&Enum
实现Default
。impl<'a> Default for &'a Enum2 { fn default() -> &'a Enum2 { &Enum2::None } }
但我不明白为什么我必须这样做。在这里,是否有一个单独的trait我应该派生,以正确使用标记的默认元素?
背景信息:目标是在列表为空时输出"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<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:
- Use
unwrap_or()
and provide a reference to the default element manually:v.last().unwrap_or(&Enum::None)
- 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?
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.
答案1
得分: 2
以下是您要的翻译内容:
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.
在您的情况下,如果必须的话,手动派生 Default
当然是可以的,这可能是我会做的。但对我来说,首先需要这样做很可能是代码中存在问题的迹象。
对于如此简单的枚举,使用非可变引用而不是值实际上是一种反模式。在 64 位系统中,引用的大小为 64
,而包含三个简单值的枚举很可能只有 8
位大。因此,使用引用会增加开销。
因此,在这种情况下,您可以使用 copied()
通过值查询对象。这很可能比通过引用查询对象要快。
#[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
无论如何,我认为您之所以首先提出这个问题,说明您的代码存在架构问题。
有两种情况:
- 您的枚举成员都包含有效数据;在这种情况下,在
.last()
中始终重要的是区分 "包含一些数据的一个元素" 和 "没有元素",并且退回到默认值可能不可行。 - 您的枚举有一个
None
成员,在这种情况下,将None
元素放在.last()
上等同于没有成员(这是我怀疑您的代码所做的)。在这种情况下,手动定义None
成员是毫无意义的,只需使用已经存在的Option
枚举。这将使您的问题变得容易解决;Option
已经实现了Default
(即None
),并可以通过.as_ref()
传播引用到其成员:
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum EnumContent<T> {
One(T),
Two(T),
}
// Option 已经实现了 `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
英文:
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 theNone
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 aNone
member, though, simply use theOption
enum that already exists. That would make your problem trivially solvable;Option
already implementsDefault
(which isNone
) 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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论