有没有办法将泛型类型限制为几种类型之一?

48
我正在尝试创建一个通用的结构体,其中使用“整数类型”引用数组。出于性能考虑,我希望能够轻松地指定是使用u16u32还是u64。像这样(显然不是有效的Rust代码):
struct Foo<T: u16 or u32 or u64> { ... }

有没有办法表达这个意思?

2个回答

40

通常情况下,将引用存储到数组中只需使用usize而不是其他整数类型。

但是,为了实现您想要的功能,您可以创建一个新的特征,将该特征实现为u16u32u64,然后将T限制为您的新特征。

pub trait MyNewTrait {}

impl MyNewTrait for u16 {}
impl MyNewTrait for u32 {}
impl MyNewTrait for u64 {}

struct Foo<T: MyNewTrait> { ... }

您可以在MyNewTrait上添加方法,以及impl来封装特定于u16u32u64的逻辑。


2
是的,但这并不允许我像使用u16、u32或u64那样做任何事情 - 例如,我不能使用一个值来检索数组元素,比较两个值等等。 - Henning Koehler
4
你可以做到这一点,只需在trait级别上声明它。即:pub trait MyNewTrait: Add<Output = Self> + Mul<Output = Self> + ... {} - Matthieu M.
我想是可以的,但这很快就会变得乏味。而且,如果要引用到一个向量中,我需要将其转换为usize,对于可以添加、乘等任意类型来说,这似乎并不是很安全的。 - Henning Koehler
3
@HenningKoehler 你可以在你的结构体或特质上添加另一个特质限制,即 Into<usize>。可以是 T: MyNewTrait + Into<usize>trait MyNewTrait : Into<usize> - Lukazoid

26
有时候,您可能希望使用枚举(enum),而不是具有特质限定的通用类型。例如:
enum Unsigned {
    U16(u16),
    U32(u32),
    U64(u64),
}

struct Foo { x: Unsigned, ... };

创建新类型相较于为现有类型实现新特质的优点之一是,您可以为新类型添加外来特质和内在行为。您可以为“Unsigned”实现任何您喜欢的特质,例如“Add”,“Mul”等。当“Foo”包含一个“Unsigned”时,实现对“Unsigned”的特质不会像将它们作为“Foo”的参数限制(例如“Foo<T: Add<Output=Self> + PartialCmp + ...>”)那样影响“Foo”的签名。另一方面,您仍然必须实现每个特质。
另一个需要注意的事情是:虽然通常情况下您总是可以创建新类型并为其实现特质,但枚举是“封闭”的:您无法在不触及其余部分的情况下向“Unsigned”添加新类型,就像使用特质一样。这可能是好事或坏事,具体取决于您的设计需求。
“性能原因”有点模糊,但如果您考虑存储许多将全部是相同内部类型的“Unsigned”,并且这:
struct Foo([Unsigned; 1_000_000]);

如果存储一百万个u16会浪费大量空间,你仍然可以将Foo设置为通用类型!只需实现From<u16>From<u32>From<u64>来支持Unsigned,然后按照以下方式编写:

struct Foo<T: Into<Unsigned>>([T; 1_000_000]);

现在你只需要对 T 进行一个简单的特质约束,不必浪费空间来存储标签和填充,并且处理 T 的函数可以始终将其转换为 Unsigned 以进行计算。转换的成本甚至可以被完全优化掉。
参见:

1
哦,太棒了。这正是我在找的哈哈。我没有意识到我可以以那种方式在枚举内部使用元组结构体,但现在我想一想,它确实非常有道理。 - Lazerbeak12345
2
只是补充一下,num crate已经为您完成了这项工作:https://docs.rs/num/latest/num/traits/trait.Unsigned.html - Anonyme2000

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接