#枚举 #内部 #宏推导 #自动 #字段 #访问 #错误

enum-try-as-inner

为枚举推导内部字段访问函数的过程宏

2个版本

0.1.1 2023年12月4日
0.1.0 2023年10月26日

2007过程宏

Download history 57/week @ 2024-04-14 143/week @ 2024-04-21 309/week @ 2024-04-28 195/week @ 2024-05-05 634/week @ 2024-05-12 563/week @ 2024-05-19 352/week @ 2024-05-26 308/week @ 2024-06-02 211/week @ 2024-06-09 167/week @ 2024-06-16 711/week @ 2024-06-23 421/week @ 2024-06-30 422/week @ 2024-07-07 329/week @ 2024-07-14 393/week @ 2024-07-21 271/week @ 2024-07-28

1,456 每月下载量
spenso 中使用

MIT/Apache

32KB
499

enum-try-as-inner

一个用于生成自动访问枚举内部成员的函数的推导过程宏。

这是一个 enum-as-inner 的分支,这个crate专注于返回 Result<Variant, EnumError>

访问器

宏会自动为每个变体生成访问字段的函数。

use enum_try_as_inner::EnumTryAsInner;

#[derive(EnumTryAsInner)]
enum MyEnum {
    Foo(u32),
    Bar(String)
}

展开后看起来像这样(省略了一些细节)

enum MyEnum {
    Foo(u32),
    Bar(String)
}

impl MyEnum {
    /// Returns true if this is a `MyEnum::Foo`, otherwise false
    pub fn is_foo(&self) -> bool {
        match self {
            Self::Foo(_) => true,
            _ => false,
        }
    }

    /// Returns references to the inner fields if this is a `MyEnum::Foo`,
    /// otherwise an `MyEnumError`
    pub fn try_as_foo(&self) -> Result<&u32, MyEnumError> {
        match self {
            Self::Foo(inner) => Ok((inner)),
            _ => {
                Err(
                    MyEnumError::new(
                        "Foo",
                        self.variant_name(),
                        None,
                    ),
                )
            }
        }
    }

    /// Returns mutable references to the inner fields if this is a `MyEnum::Foo`, 
    /// otherwise an `MyEnumError`
    pub fn try_as_foo_mut(&mut self) -> Result<&mut u32, MyEnumError> {
        match self {
            Self::Foo(inner) => Ok((inner)),
            _ => {
                Err(
                    MyEnumError::new(
                        "Foo",
                        self.variant_name(),
                        None,
                    ),
                )
            }
        }
    }

    /// Returns the inner fields if this is a `MyEnum::Foo`, otherwise returns
    /// back the enum in the `Err` case of the result
    #[inline]
    pub fn try_into_foo(self) -> Result<u32, MyEnumError> {
        match self {
            Self::Foo(inner) => Ok((inner)),
            _ => {
                Err(
                    MyEnumError::new(
                        "Foo",
                        self.variant_name(),
                        Some(self),
                    ),
                )
            }
        }
    }
    
    /// Returns the name of the variant.
    fn variant_name(&self) -> &'static str {
        match self {
            Self::Foo(..) => "Foo",
            Self::Bar(..) => "Bar",
            _ => ::core::panicking::panic("internal error: entered unreachable code"),
        }
    }

    // .. Omitted methods for `Bar` variant
}

错误

宏生成一个错误类型,提供有关预期哪个变体、运行时找到哪个变体以及如果使用 try_into_* 函数,实际值的详细信息。

展开后看起来像这样(省略了一些细节)

enum MyEnum {
    Foo(u32),
    Bar(String)
}

struct MyEnumError {
    expected: &'static str,
    actual: &'static str,
    value: Option<MyEnum>,
}

impl MyEnumError {
    /// Creates a new error indicating the expected variant and the actual variant.
    fn new(
        expected: &'static str,
        actual: &'static str,
        value: Option<MyEnum>,
    ) -> Self {
        Self { expected, actual, value }
    }

    /// Returns the name of the variant that was expected.
    pub fn expected(&self) -> &'static str {
        self.expected
    }

    /// Returns the name of the actual variant.
    pub fn actual(&self) -> &'static str {
        self.actual
    }

    /// Returns a reference to the actual value, if present.
    pub fn value(&self) -> Option<&MyEnum> {
        self.value.as_ref()
    }

    /// Returns the actual value, if present.
    pub fn into_value(self) -> Option<MyEnum> {
        self.value
    }
}

错误推导

默认情况下,生成的错误不实现任何特质,包括 std::error::Error

可以使用 derive_err 属性将推导宏传递到错误实现。

如果提供了 Debug 推导,将自动提供 DisplayError 的实现。

如果您想实现自己的 Display 格式,您还需要自己实现 DebugError

use enum_try_as_inner::EnumTryAsInner;

#[derive(Debug, Clone, PartialEq, EnumTryAsInner)]
#[derive_err(Debug, Clone, PartialEq)]
enum Clonable {
    One(u32),
    Two(u32, i32),
}

let one = Clonable::One(1);
let err = one.try_into_two().unwrap_err();
let cloned = err.clone();

assert_eq!(err, cloned);

println!("expected {}, but got {}", err.expected(), err.actual());
println!("actual value: {:?}", err.into_value().unwrap());

示例

基本无名称字段案例

基本案例适用于单个项目枚举,如

use enum_try_as_inner::EnumTryAsInner;

#[derive(Debug, EnumTryAsInner)]
#[derive_err(Debug)]
enum OneEnum {
    One(u32),
}

let one = OneEnum::One(1);

assert_eq!(*one.try_as_one().unwrap(), 1);
assert_eq!(one.try_into_one().unwrap(), 1);

结果要么是内部项的引用,要么是包含内部项的元组。

单元案例

如果枚举的变体与预期类型匹配,这将返回 true

use enum_try_as_inner::EnumTryAsInner;

#[derive(EnumTryAsInner)]
enum UnitVariants {
    Zero,
    One,
    Two,
}

let unit = UnitVariants::Two;

assert!(unit.is_two());
assert!(unit.try_as_two().is_ok());

多个,未命名的字段案例

这将返回一个包含内部类型的元组

use enum_try_as_inner::EnumTryAsInner;

#[derive(Debug, EnumTryAsInner)]
#[derive_err(Debug)]
enum ManyVariants {
    One(u32),
    Two(u32, i32),
    Three(bool, u32, i64),
}

let many = ManyVariants::Three(true, 1, 2);

assert!(many.is_three());
assert_eq!(many.try_as_three().unwrap(), (&true, &1_u32, &2_i64));
assert_eq!(many.try_into_three().unwrap(), (true, 1_u32, 2_i64));

多个,命名的字段案例

这将返回一个包含内部类型的元组,类似于未命名的选项

use enum_try_as_inner::EnumTryAsInner;

#[derive(Debug, EnumTryAsInner)]
#[derive_err(Debug)]
enum ManyVariants {
    One { one: u32 },
    Two { one: u32, two: i32 },
    Three { one: bool, two: u32, three: i64 },
}

let many = ManyVariants::Three { one: true, two: 1, three: 2 };

assert!(many.is_three());
assert_eq!(many.try_as_three().unwrap(), (&true, &1_u32, &2_i64));
assert_eq!(many.try_into_three().unwrap(), (true, 1_u32, 2_i64));

状态机

此示例演示了如何使用 derive 宏帮助构建交通灯的状态机。

// This is similar to the typestate pattern, but here the state is wrapped in an enum so that
// the internal state is not part of the type signature of `TrafficLight`.
#[derive(Debug, Default)]
pub struct TrafficLight {
    state: state::State,
}

#[derive(Debug)]
pub enum TrafficLightError {
    InvalidState(String),
    NotEnoughCarsPassed(usize),
    NotEnoughCarsWaiting(usize),
}

// This lets us propagate state errors easily using the `?` operator.
impl From<state::StateError> for TrafficLightError {
    fn from(err: state::StateError) -> Self {
        TrafficLightError::InvalidState(err.to_string())
    }
}

// Every state transition/update is guarded by a runtime check
impl TrafficLight {
    pub fn turn_red(&mut self) -> Result<(), TrafficLightError> {
        // Only a yellow light can turn red.
        self.state.try_as_yellow()?;

        self.state = state::State::Red(state::Red::default());

        Ok(())
    }

    pub fn turn_yellow(&mut self) -> Result<(), TrafficLightError> {
        // Only a green light can turn yellow.
        // This serves as both a runtime state-guard as well as an accessor
        // for the state variables.
        let &state::Green { cars_passed } = self.state.try_as_green()?;

        if cars_passed > 10 {
            self.state = state::State::Yellow;
        } else {
            return Err(TrafficLightError::NotEnoughCarsPassed(cars_passed));
        }

        Ok(())
    }

    pub fn turn_green(&mut self) -> Result<(), TrafficLightError> {
        // Only a red light can turn green.
        let &state::Red { cars_waiting } = self.state.try_as_red()?;

        if cars_waiting > 0 {
            self.state = state::State::Green(state::Green::default());
        } else {
            return Err(TrafficLightError::NotEnoughCarsWaiting(cars_waiting));
        }

        Ok(())
    }

    pub fn record_passed_car(&mut self) -> Result<(), TrafficLightError> {
        // Passing cars are only recorded when the light is green.
        let state::Green { cars_passed } = self.state.try_as_green_mut()?;

        *cars_passed += 1;

        Ok(())
    }

    pub fn record_waiting_car(&mut self) -> Result<(), TrafficLightError> {
        // Waiting cars are only recorded when the light is red.
        let state::Red { cars_waiting } = self.state.try_as_red_mut()?;

        *cars_waiting += 1;

        Ok(())
    }
}

mod state {
    use enum_try_as_inner::EnumTryAsInner;

    #[derive(Debug, EnumTryAsInner)]
    #[derive_err(Debug)]
    pub enum State {
        Red(Red),
        Yellow,
        Green(Green),
    }

    impl Default for State {
        fn default() -> Self {
            State::Red(Red::default())
        }
    }

    #[derive(Debug, Default)]
    pub struct Red {
        pub cars_waiting: usize,
    }

    #[derive(Debug, Default)]
    pub struct Green {
        pub cars_passed: usize,
    }
}

let mut light = TrafficLight::default();

light.record_waiting_car().unwrap();

// Can not transition from red to yellow.
assert!(light.turn_yellow().is_err());

light.turn_green().unwrap();

for _ in 0..=9 {
    light.record_passed_car().unwrap();
}

// At least 10 cars must have passed to turn yellow.
assert!(light.turn_yellow().is_err());

light.record_passed_car().unwrap();

light.turn_yellow().unwrap();
light.turn_red().unwrap();

依赖项

~285–730KB
~17K SLoC