#枚举 #变体 #常量 #const #proc-macro #macro-derive

enum-assoc

将常量与枚举变体关联的过程宏

18 个版本 (2 个稳定版)

1.1.0 2023 年 4 月 23 日
1.0.0 2023 年 3 月 21 日
0.4.0 2022 年 10 月 17 日
0.3.4 2022 年 5 月 10 日
0.1.8 2022 年 2 月 21 日

#1397 in Rust 模式

Download history 1334/week @ 2024-03-13 1009/week @ 2024-03-20 1280/week @ 2024-03-27 856/week @ 2024-04-03 888/week @ 2024-04-10 910/week @ 2024-04-17 1385/week @ 2024-04-24 1210/week @ 2024-05-01 1279/week @ 2024-05-08 1157/week @ 2024-05-15 1157/week @ 2024-05-22 1010/week @ 2024-05-29 865/week @ 2024-06-05 1174/week @ 2024-06-12 1426/week @ 2024-06-19 1558/week @ 2024-06-26

5,267 每月下载次数
用于 7 个 crate (5 个直接使用)

MIT/Apache

26KB
363

enum-assoc

此 crate 定义了一些宏,允许您将常量或数据与枚举变体关联。

使用时,必须将 #[derive(Assoc)] 附加到枚举上。从那里,使用 func 属性来定义将为该枚举实现的功能签名。使用 assoc 属性来定义在调用该函数时每个变体将返回的常量。

正向关联

以下是一个示例

use enum_assoc::Assoc;

const WA: &'static str = "wa";

#[derive(Assoc)]
#[func(pub const fn foo(&self) -> u8)]
#[func(pub fn bar(&self) -> &'static str)]
#[func(pub fn maybe_foo(&self) -> Option<u8>)]
#[func(pub fn with_default(&self) -> u8 { 4 })]
enum TestEnum {
    #[assoc(foo = 255)]
    #[assoc(bar = "wow")]
    Variant1,
    #[assoc(foo = 1 + 7)]
    #[assoc(bar = "wee")]
    #[assoc(with_default = 2)]
    Variant2,
    #[assoc(foo = 0)]
    #[assoc(bar = WA)]
    #[assoc(maybe_foo = 18 + 2)]
    Variant3
}

fn main() {
    println!("Variant1 foo: {}", TestEnum::Variant1.foo());
    println!("Variant2 foo: {}", TestEnum::Variant2.foo());
    println!("Variant3 foo: {}", TestEnum::Variant3.foo());
    println!("Variant1 bar: {}", TestEnum::Variant1.bar());
    println!("Variant2 bar: {}", TestEnum::Variant2.bar());
    println!("Variant3 bar: {}", TestEnum::Variant3.bar());
    println!("Variant1 maybe_foo: {:?}", TestEnum::Variant1.maybe_foo());
    println!("Variant2 maybe_foo: {:?}", TestEnum::Variant2.maybe_foo());
    println!("Variant3 maybe_foo: {:?}", TestEnum::Variant3.maybe_foo());
    println!("Variant1 with_default: {:?}", TestEnum::Variant1.with_default());
    println!("Variant2 with_default: {:?}", TestEnum::Variant2.with_default());
    println!("Variant3 with_default: {:?}", TestEnum::Variant3.with_default());
}

输出

Variant1 foo: 255
Variant2 foo: 8
Variant3 foo: 0
Variant1 bar: wow
Variant2 bar: wee
Variant3 bar: wa
Variant1 maybe_foo: None
Variant2 maybe_foo: None
Variant3 maybe_foo: Some(20)
Variant1 with_default: 4
Variant2 with_default: 2
Variant3 with_default: 4

请注意,返回 Option 类型函数的特殊功能:变体可以完全省略 assoc 属性以自动返回 None,并且产生值的变体无需显式将其包装在 Some 中。

这个输出是什么意思?

每个 #[func(fn_signature)] 属性生成如下内容

impl Enum {
    fn_signature {
        match self {
            // ... arms
        }
    }
}

并且每个 #[assoc(fn_name = association)] 属性为其关联函数生成如下臂

    variant_name => association,

就是这样。您使用的 fn_signature 的详细信息以及您在 association 区域中放置的内容完全由您决定。

因此,虽然技术上不是此 crate 的原始意图,但您可以免费生成一些更有趣/更复杂的关联

use enum_assoc::Assoc;

#[derive(Assoc)]
#[func(pub fn foo(&self, param: u8) -> Option<u8>)]
#[func(pub fn bar(&self, param: &str) -> String)]
#[func(pub fn baz<T: std::fmt::Debug>(&self, param: T) -> Option<String>)]
enum TestEnum2 {
    #[assoc(bar = String::new() + param)]
    Variant1,
    #[assoc(foo = 16 + param)]
    #[assoc(bar = String::from("Hello") + param)]
    Variant2,
    #[assoc(bar = some_str_func(param))]
    #[assoc(baz = format!("{:?}", param))]
    Variant3
}

fn some_str_func(s: &str) -> String {
    String::from("I was created in a function") + s
}

fn main() {
    println!("Variant1 foo: {:?}", TestEnum2::Variant1.foo(0));
    println!("Variant2 foo: {:?}", TestEnum2::Variant2.foo(22));
    println!("Variant1 bar: {}", TestEnum2::Variant1.bar("string"));
    println!("Variant2 bar: {}", TestEnum2::Variant2.bar(" World!"));
    println!("Variant3 bar: {}", TestEnum2::Variant3.bar("!"));
    println!("Variant3 baz: {:?}", TestEnum2::Variant3.baz(1));
}

输出

Variant1 foo: None
Variant2 foo: 34
Variant1 bar: string
Variant2 bar: Hello World!
Variant3 bar: I was created in a function!
Variant3 baz: Some("1")

assoc 属性中访问枚举字段

在关联属性中,通过在其名称前加下划线,可以访问枚举变体的字段值。对于元组,名称由下划线前缀和字段索引组成。

use thiserror::Error;

#[derive(Error, Debug, Assoc, Clone)]
#[func(pub const fn status(&self) -> u16)]
pub enum ServiceError {
    #[error("failed to start or finish a transaction")]
    #[assoc(status = 500)]
    TransactionError { source: sea_orm::DbErr },

    #[error(transparent)]
    #[assoc(status = _source.status())]
    RequestParsingError {
        source: request_parser::RequestParsingError,
    },
}

mod request_parser {
    use super::*;

    #[derive(Error, Debug, Assoc, Clone)]
    #[func(pub const status(&self) -> u16)]
    pub enum RequestParsingError{
        #[error("provided input was too large")]
        #[assoc(status = 500)]
        OutOfMemory
        #[error("the resource id did not have correct format")]
        #[assoc(status = 400)]
        InvalidResourceIdFormat
        #[error("external validator service returned an error")]
        #[assoc(status = _0.status())]
        ExternalValidatorError(validator::Error)
    }
}

pub mod validator{
    //...
}

反向关联

这也可以生成反向关联(常量到枚举变体)。以下是一个示例。

use enum_assoc::Assoc;

#[derive(Assoc, Debug)]
#[func(pub fn foo(s: &str) -> Option<Self>)]
#[func(pub fn bar(u: u8) -> Self)]
#[func(pub fn baz(u1: u8, u2: u8) -> Self)]
enum TestEnum3
{
    #[assoc(foo = "variant1")]
    #[assoc(bar = _)]
    Variant1,
    #[assoc(bar = 2)]
    #[assoc(foo = "variant2")]
    #[assoc(baz = (3, 7))]
    Variant2,
    #[assoc(foo = "I'm variant 3!")]
    #[assoc(foo = "variant3")]
    #[assoc(baz = _)]
    Variant3
}

fn main()
{
    println!("TestEnum3 foo(\"variant1\"): {:?}", TestEnum3::foo("variant1"));
    println!("TestEnum3 foo(\"variant3\"): {:?}", TestEnum3::foo("variant3"));
    println!("TestEnum3 foo(\"I'm variant 3!\"): {:?}", TestEnum3::foo("I'm variant 3!"));
    println!("TestEnum3 foo(\"I don't exist\"): {:?}", TestEnum3::foo("I don't exist"));
    println!("TestEnum3 bar(2): {:?}", TestEnum3::bar(2));
    println!("TestEnum3 bar(55): {:?}", TestEnum3::bar(55));
    println!("TestEnum3 baz(3, 7): {:?}", TestEnum3::baz(3, 7));
    println!("TestEnum3 baz(0, 0): {:?}", TestEnum3::baz(0, 0));
}

输出

TestEnum3 foo("variant1"): Some(Variant1)
TestEnum3 foo("variant3"): Some(Variant3)
TestEnum3 foo("I'm variant 3!"): Some(Variant3)
TestEnum3 foo("I don't exist"): None
TestEnum3 bar(2): Variant2
TestEnum3 bar(55): Variant1
TestEnum3 baz(3, 7): Variant2
TestEnum3 baz(0, 0): Variant3

反向关联与正向关联工作方式略有不同

  • 反向关联不得包含一个 self 参数(没有 self 参数是正向关联与反向关联的区别所在)
  • 它们必须返回 SelfOption<Self>
  • 与正向关联不同,单个枚举变体可以定义任何数量的相同函数的 assoc 属性。
  • 与正向关联不同,assoc 属性定义了一个模式而不是一个表达式。这是因为反向关联控制匹配臂的左侧而不是右侧。
  • 生成的函数将匹配包含所有函数参数的元组。
  • 匹配臂将按从上到下的顺序排列,只有一个例外:任何通配符模式 _ 总是放在底部。
  • 任何反向关联函数的通配符关联不能超过1个。超过1个将导致编译错误。
  • 如果为返回 Option<Self> 的函数未定义通配符模式,则会自动插入 _ => None 肢。

因此,为了简单反向关联生成有效的代码,必须满足以下3个条件之一

  1. 反向关联返回 Option<Self>,或
  2. 为正好一个变体定义了通配符(_)模式,或
  3. 每个可能的值都映射到一个枚举变体
  • 注意:对于返回多个参数的反向关联,可以使用通配符为特定参数指定(例如 (5, _))。此宏不会像处理通配符(_)那样重新排序这些参数。匹配臂将放置在枚举属性列中它出现的确切位置。

目前,没有方法可以将反向关联映射到元组或类似于结构的变体。

这个输出是什么意思?

每个反向关联的 #[func(fn_signature)] 属性生成如下内容

impl Enum {
    fn_signature {
        match (param1, param2, etc) {
            // ... arms
        }
    }
}

并且每个反向关联的 #[assoc(fn_name = pattern)] 属性为其关联的函数生成如下臂

    pattern => variant_name,

依赖项

~1.5MB
~35K SLoC