5 个版本
0.1.4 | 2024 年 3 月 18 日 |
---|---|
0.1.3 | 2024 年 3 月 17 日 |
0.1.2 | 2024 年 3 月 17 日 |
0.1.1 | 2024 年 3 月 17 日 |
0.1.0 | 2024 年 3 月 16 日 |
731 在 Rust 模式
每月 181 次下载
47KB
672 行
summum-types
一个包含所有转换、访问器和跨变体抽象方法支持以及和类型间互操作性的 sum-type 宏库
摘要
此crate致力于在Rust的静态编译时类型之上提供动态运行时解析类型,并完全支持泛型、生命周期、可见性等。
summum
宏允许轻松声明具有以下特性和方法的sum类型:
- 转换和访问的预期特性和方法
- 允许在所有变体上实现泛型方法
- 支持通过共享变体名称在不同类型间进行互操作
动机
Rust的
enum
已经是sum类型,那我为什么还需要这个crate呢?
为你编写了大量样板代码。
当我尝试实现递归类型定义时,我意识到我需要这样的东西。 Rust的静态类型系统无法表示我需要的类型,而不强制有限的递归深度。但是使用enum
使我的实现加倍,因为变体之间的单态化不支持。
Summum??
这只是个愚蠢的双关语。它在拉丁语中意味着“最高”。与犹他州的夏穆姆人毫无关系。
用法
在summum
宏内定义一个sum类型,但除此之外,它就像任何其他enum一样
summum!{
#[derive(Debug, Clone)]
enum SliceOrPie<'a, T> {
Slice(&'a [T]),
Vec(Vec<T>),
}
}
并且你自动获得所有想要的访问器¹
- From
impl
从每个变体创建sum类型 - TryFrom
impl
,将sum类型转换回任何变体。² pub fn is_*t*(&self) -> bool
pub fn try_as_*t*(&self) -> Option<&T>
pub fn as_*t*(&self) -> &T
pub fn try_as_mut_*t*(&mut self) -> Option<&mutT>
pub fn as_mut_*t*(&mut self) -> &mutT
pub fn try_into_*t*(self) -> Result<T,Self>
pub fn into_*t*(self) ->T
pub fn variant_name(&self) -> &'static str
pub fn SumT::variants() -> &[&str]
注意:*t*
是变体标识符的lower_snake_case渲染形式,而SumT
是你定义的类型
¹如果您需要更多的访问器(或一般功能),请给我发电子邮件
²除非变体类型是一个“未覆盖”的泛型,如此处所述
泛型方法实现调度
您还可以添加方法impl
块,以实现您在求和类型中每个变体之间共享的功能。这扩展为一个针对&self
的匹配语句,其中&self
被映射为内部变体类型的局部变量。例如
summum!{
#[derive(Debug, Clone)]
enum SliceOrPie<'a, T> {
Slice(&'a [T]),
Vec(Vec<T>),
}
impl<'a, T> SliceOrPie<'a, T> {
fn get(&self, idx: usize) -> Option<&T> {
self.get(idx)
}
}
}
您还可以使用InnerT
作为局部类型别名,它展开为变体的内部类型。Self
将继续引用整个外部类型。如果您的返回值是Self
,您需要记住使用.into()
转换来返回求和类型。如下所示
summum!{
enum Num {
F64(f64),
I64(i64),
}
impl Num {
fn max_of_type(&self) -> Self {
InnerT::MAX.into()
}
}
}
是的,所有抽象方法都需要self
来知道使用哪个变体类型。您还可以使用变体专用方法(继续阅读...)来构造函数和其他不需要self
参数的地方。
当然,您也可以在summum
调用之外实现子类型的普通方法,在这些行为不适用。
变体专用方法
有时您需要为每个变体生成一个显式的方法。summum
可以满足您的需求。只需在方法名末尾添加"_inner_var"
,它将为每个变体生成一个单独的方法。例如,下面的代码将生成max_f64
和max_i64
方法。
summum!{
enum Num {
F64(f64),
I64(i64),
}
impl Num {
fn max_inner_var() -> Self {
InnerT::MAX.into()
}
}
}
您还可以将self
作为参数传递给变体专用方法。但是请注意,如果self
的内部类型与方法变体不一致,则该方法将引发恐慌!
在变体专用方法中,您可以在函数签名中使用InnerT
,作为参数和方法返回类型。例如
//Within the `summum` invocation above...
impl Num {
fn multiply_add_one_inner_var(&self, multiplier: InnerT) -> InnerT {
*self * multiplier + 1 as InnerT
}
}
多态
Sum-type枚举的一个用途是在多态方法调度中扮演类似于dyn
特征对象的类似角色。Sum-type枚举提供了不同的设计约束,例如Sized
和不需要对象安全性。与特定的Any特征不同,summum类型提供了一个方法来恢复原始类型的所有权,并允许内部生命周期(没有'static
限制)。
Sum-type绝对不是每种情况下动态调用的替代品,但希望它们将是方便时可以使用的另一个工具。
在类型间互操作的方法调用中的变体替换
考虑相互交互的多种类型,如下例所示。有时我们需要根据正在生成的变体与相关类型交互。在这些情况下,只要被调用类型的变体名称是调用类型 impl
的超集,我们就可以调用其他类型的合成变体特定函数。
summum!{
enum Num {
F64(f64),
I64(i64),
}
enum NumVec {
F64(Vec<f64>),
I64(Vec<i64>),
}
impl NumVec {
fn push(&mut self, item: Num) {
// This will be replaced with either `into_f64` or `into_i64`,
// depending on the variant branch being generated
let val = item.into_inner_var();
self.push(val);
}
}
}
限制和排除控制指令
有时在某些变体的上下文中,条件分支根本不合理,且该分支中的代码永远不会被执行。不必要的代码很糟糕,但如果垃圾代码中的错误阻止了项目的其余部分编译,那就更糟糕了。
请使用 exclude!
和 restrict!
指令。您可以使用它们表示“这些变体永远不会到达这里”(排除)或“只有这些变体会到达这里”(限制)。
这些控制指令可以用来删除整个变体,但也可以用在更窄的运行时范围内。一个 restrict!
或 exclude!
指令实际上删除了 Block
中剩余的代码(直到 '}'
括号结束为止的所有代码),但不会影响方法中的其他代码。
以下是一个示例
summum!{
enum Num {
F64(f64),
I64(i64),
}
impl Num {
fn multiply_int_only(&self, other: i64) -> Self {
summum_restrict!(I64);
(*self * other).into()
}
fn convert_to_float_without_rounding(&self) -> f64 {
if *self > i32::MAX as InnerT {
summum_exclude!(I64, ); //You can supply multiple variants
*self as f64
} else {
*self as f64
}
}
}
}
附加语法:Haskell/TypeScript 风格
如果您喜欢简洁性,您可以编写
summum!{
type Num = f64 | i64;
}
您可以使用 as
关键字使用此语法重命名变体
summum!{
type VecOrVRef<'a, V> = &'a Vec<V> as Vec |
&'a V as V;
}
限制
-
此宏可以生成 大量 代码,其中大部分将被消除。如果过度使用,可能会降低构建时间。此外,最大类型可能不适合在公共 API 中公开,但具体情况可能有所不同。
-
impl
块必须在定义类型的同一summum!
宏调用中。这是summum
不是属性宏的主要原因。这种限制是由于 此问题 以及可能的解决方案¹,这可能会比仅仅将 impls 保留在一起更加脆弱和糟糕的体验。 -
每个内部类型在最大类型中只应出现一次。此 crate 的目的是在多个类型上实现运行时动态性。如果您想基于同一类型创建多个变体,则可以定义类型别名。或者尝试 Natasha England-Elbro 的 typesum。
¹通过两次宏展开实现宏展开是可能的,其中第二个宏在运行时创建,并融合源代码中的信息。但这就像一个鲁比·戈德堡机器。
未来工作
抽象方法声明
在多态方法调用的精神下,我希望支持没有主体的“特质风格”方法声明。这只是现有抽象 impl
调用的语法糖,但它会使抽象最大类型的方法声明看起来更加干净。
每个变体的关联类型
我希望通过关联类型别名来支持访问每个变体的类型。相对于上面示例中的Num
,声明还将包括以下内容:type F64T = f64
。这有什么用呢?单看本身,作用不大。但是,结合其他类型实现能够通过共享变体引用此类型的能力,使用::VariantT
类型别名,你就可以做这样的事情。
summum!{
enum Num {
F64(f64),
I64(i64),
}
enum NumVec {
F64(Vec<f64>),
I64(Vec<i64>),
}
impl NumVec {
fn push_inner_var(&mut self, item: Num::VariantT) {
self.push(item)
}
fn get_or_default(&self, idx: usize) -> Num {
self.get(idx).cloned().unwrap_or_else(|| Num::VariantT::default() ).into()
}
}
}
由于这个问题[1],这个特性目前已被禁用。希望它很快就能达到稳定版本,我就能重新启用它。
访问器未来的计划
我希望实现泛型访问器,类似于以下代码:pub fn try_into_sub<T>(self) -> Option<T>
,例如。这将消除记住/猜测特定变体分配的标识符的烦恼。不幸的是,目前这好像被这个问题阻止了。
认可及其他选项
存在几个其他联合类型/求和类型crate,其中之一可能更适合您的用例。每个crate都有其独特之处,我从它们中都得到了灵感。
- 如果您需要轻量级的东西,Antonius Naumann的[2]typeunion非常不错,它有一个很棒的超类型功能,可以在具有公共变体的类型之间进行自动转换。
- Michael F. Bryan的[3]sum_type是
no-std
,它通过声明性宏实现了所有功能。它还支持向下转换,可以实现对实现了Any
特质的变体类型的向下转换。 - Natasha England-Elbro的[4]typesum非常棒,它提供了对生成输出的控制,以及它以美妙的方式支持重叠基类型。如果您计划有大量变体,它将表现得更好。
最后,感谢您查看这个crate。如果您有任何想法或反馈,请通过电子邮件或GitHub issue告诉我。
依赖关系
~305–760KB
~18K SLoC