#sum-types #enums #macro #accessor #union-types #type-union

summum-types

一个包含所有转换、访问器和跨变体抽象方法支持以及和类型间互操作性的 sum-type 宏库

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 日

731Rust 模式

Download history 254/week @ 2024-03-11 172/week @ 2024-03-18 23/week @ 2024-04-01

每月 181 次下载

MIT/Apache

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_f64max_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_typeno-std,它通过声明性宏实现了所有功能。它还支持向下转换,可以实现对实现了Any特质的变体类型的向下转换。
  • Natasha England-Elbro的[4]typesum非常棒,它提供了对生成输出的控制,以及它以美妙的方式支持重叠基类型。如果您计划有大量变体,它将表现得更好。

最后,感谢您查看这个crate。如果您有任何想法或反馈,请通过电子邮件或GitHub issue告诉我。

依赖关系

~305–760KB
~18K SLoC