#enums #traits #proc-macro #derive #enumizer

no-std trait-enumizer

基于方法签名的枚举自动生成宏(带适当的助手函数)

2 个版本

0.1.1 2022年4月17日
0.1.0 2022年3月16日

Rust 模式 中排名第 712

MIT/Apache

40KB
224

trait-enumizer

img

从特质生成枚举,提供它们之间的转换器。

特性

  • 基于指定的特质(或内建实现)生成枚举,其中每个变体对应一个方法。参数对应于变体的字段;返回值对应于某个通道的发送者。
  • 生成具有适当 match 的调用函数,这些函数在内部生成。这使得枚举可以“应用于”实现该特质的对象。
  • 生成代理,允许通过原始特质的方法调用(如果可能)或类似原始的 API 获取枚举值的序列。代理还有助于处理“通道化”返回值。
  • 处理返回值。
  • 处理异步(仅限内建实现)。
  • 我尝试使 crate 与 no_std 兼容,但尚未测试。

该库可以用作同步机制,也可以用作构建actor或远程过程调用的构建块。

库的主要部分是 enumizer 属性宏。它应该附加到特质或内建实现。它将输入特质或实现复制到 proc macro 输出(不带伪 derive-helpers 属性),然后还生成以下项

  • 代表方法的枚举
  • 零个或多个“调用函数”
  • 零个或多个“代理结构体”

参数

#[trait_enumizer::enumizer(...)] 接受以下参数

  • name=<ident> - 设置枚举名称。必需。
  • pubpub_crate - 分别将所有生成的项标记为 pubpub(crate)
  • returnval=<macro_class_name> - 启用更复杂的模式,其中处理返回值。会影响其他生成项(调用函数和代理)的API。输入宏用作临时代替GAT特质的特殊化。请参阅专门的README部分以获取更多信息。
  • enum_attr - 将自定义属性(例如 enum_attr[derive(serde_derive::Serialize)])注入枚举声明中。可以重复。您需要使用方括号来使用它。
  • inherent_impl - 以固有实现为基础枚举,而不是以特质为基础。
  • call_fn() - 请见下文。
  • proxy() - 请见下文。

属性语法示例

#[trait_enumizer::enumizer(pub_crate, name=NewEnumName, call_fn(name=call_me,ref_mut,allow_panic), proxy(name=NewEnumNameProxy, Fn,unwrapping_impl))]

调用函数(call_fn

当您使用 call_fn() 参数时,会生成调用函数。它们使用以下子参数

  • name=<ident> - 内置方法的名称。必需。
  • refref_mut(别名 mut_ref)或 once(别名 move) - 通过引用、可变引用或值接受对象。必需。
  • allow_panic - 允许在函数内部生成 panic!() 调用。
  • async - 生成 async fn。使用 send_async 伪方法从 returnval 宏类代替 send
  • extra_arg_type(<type>) - 向 try_call 函数添加附加参数。该参数将出现在所有 macro_class_name!(send(...)) 回调中。

这些函数用于将枚举值“转换为”方法调用。调用函数作为生成枚举的固有实现函数生成。第一个参数是 self。第二个参数是您指定的特质(在 inherent_impl 模式下跳过特质)的实现(或值的引用)。第三个参数在指定 extra_arg_type() 时是必需的。它传递给 returnval 的 send(或 send_async)伪方法,以便对返回值进行自定义处理。

示例

#[enumizer(name=QqqEnum,pub,call_fn(name=the_call,ref_mut))]
trait Qqq {
    ...
}

生成

enum QqqEnum { ... }
impl QqqEnum {
    pub fn the_call<I: Qqq>(self, o: &mut I);
}

代理

当您使用 proxy() 参数时,会生成代理。它们使用以下子参数

  • FnFnMutFnOnce - 设置代理将携带的闭包的类型。必需。
  • name=<ident> - 生成结构的名称。必需的。
  • resultified_trait=<ident> - 除了实现 try_* 函数外,还生成“resultified”特质。
  • infallible_impl - 如果您的 sink 函数不会出错,则使代理也实现原始特质。
  • unwrapping_impl - 使代理也实现原始特质,在需要的地方使用 unwrap
  • unwrapping_and_panicking_impl - 强制代理实现原始特质,在由于所有权要求而失败的地方使用 panic!() 调用。
  • extra_field_type(...) - 向代理结构添加额外的第二个字段。该字段将用作 macro_class_name!(create(...))macro_class_name!(recv(...)) 回调的额外参数。
  • async - 期望用户指定的闭包返回 Future<Output=Result> 而不是仅仅返回 Result,并在适当的地方使用 .await

代理是一个具有公共字段的泛型元组结构。该字段应实现 FnFnMutFnOnce。如果指定了 extra_field_type(),则创建第二个字段(也是公共的)。有两个泛型参数:错误类型(您选择它)和闭包类型。代理允许将方法调用“转换”为枚举值(这些值被传递到您的闭包)。默认情况下,所有输入方法都被重命名,以“try_”开头。通常,它们返回 Result<(), YourErrorType>,但在 returnval 模式下,其中一些可能返回 Result<Result<T, SendError>, YourErrorType>。存在异步模式,它将您的函数升级为返回 Future,并将所有 try_* 方法转换为 async。您还可以要求 Enumizer 生成代理实现“resultified”特质(当然,除非 async)。async 还会影响 returnval 宏的使用。

您还可以要求 Enumizer 使代理实现原始特质(当然,除非 async)。有两种策略:不可失败(如果未使用返回值,并且您的 Fn 通过使用 std::convert::Infallible 退出错误处理)和 unwrapping。

您可以将非异步原始方法转换为异步代理,反之亦然。

示例(简化版)

#[enumizer(name=QqqEnum,proxy(FnMut,name=QqqProxy))]
trait Qqq {
    fn foo(&self,x : i32);
}

生成

enum QqqEnum { Foo{x:i32} }
struct QqqProxy<E,F>(pub F)
    where F: FnMut(QqqEnum) -> Result<(), E>;
impl<E,F> QqqProxy where ... {
    fn try_foo(&mut self, x: i32) -> Result<(), E>{
        (self.0)(QqqEnum::Foo{x})
    }
}

伪衍生的辅助工具

伪派生助手是此库处理的属性。您应该在输入特性和 impl 中使用它们,在方法签名之前或签名内的参数之前。其他(未知)属性将未经修改地传递。

  • #[enumizer_enum_attr[...]] - 将指定的属性转发到生成的枚举。例如:#[enumizer_enum_attr[serde(rename="qqq")]]。可以附加到函数(成为枚举变体)或函数参数(成为枚举变体字段)。
  • #[enumizer_return_attr[...]] - 在 returnval 模式下,将自定义属性附加到枚举的 ret 字段。
  • #[enumizer_to_owned] - 对于引用参数类型,使用所有者值而不是尝试将引用放入枚举(可能不起作用,除非 'static)。

Returnval伪特异

如果您想让 Enumizer 处理返回值,您需要一个某种类型的通道。Enumizer 在通道选择方面非常灵活。有一些内置的“类”用于一些流行的通道类型,您可能还需要自己实现一个通道类。

您将通道类指定为 returnval 参数的值,例如 returnval=trait_enumizer::flume_class。在 Enumizer 设计的早期,通道类是使用 GAT 的特质,但现在它们是特殊的基于 macro_rules 的宏。

以下是通道类的 API

macro_rules! my_channelclass {
    (Sender<$T:ty>) => { /* type of the `ret` field in enum variant */ };
    (SendError) => { /* Error type when failed to send to a channel. Must not depend on T */ };
    (RecvError) => { /* Error type when failed to receive from a channel */ };
    (create::<$T:ty>()) => {
         /* Expression returning (tx, rx) channel pair. `tx` must be of type `Sender`. */
         /* Used by proxies */
    };
    (send::<$T:ty>($channel:expr, $msg:expr /*, $extraarg:expr */)) => { 
         /* Expression used to send to the channel (for `call_fn`). You may need to map error type here */
    };
    (recv::<$T:ty>($channel:expr /*, $extrafield:expr */)) => { 
        /* Expression use to recv value from the channel (for proxy) */
     };
    (send_async::<$T:ty>($channel:expr, $msg:expr)) => { 
        /* Expression to send to channel from async `call_fn`s. Should include `.await` and error mapping */
    };
    (recv_async::<$T:ty>($channel:expr)) => { 
        /* Expression to receive from cahnnel in async proxies. Should include `.await`. */
     };
}

建议您根据自己的实现基于内置的通道类(例如 flume_class)或使用 RPC 示例作为更复杂通道类的模板。

尽管 returnval 机制使用“通道”术语,但 Sender 不需要是实际的通道。它们可能是某些内部 ID,而真实的通道则作为附加参数提供。

当 Enumizer 遇到具有返回值的方法时,相应的枚举变体将获得一个名为 ret(一个硬编码的标识符)的附加字段。该字段的类型由通道类控制,并且可能取决于返回值的类型。与该附加字段的全部交互都通过通道类的伪方法进行。

测试(也作为文档使用)

要了解该软件包的工作原理,您可以查看一些测试文件。对于大多数示例,都有一个相应的“手动”示例,显示了相同测试的展开版本(有时略有简化)。

请注意,这些链接在 Docs.rs 上可能已损坏,请使用 Github 上的 README 代替。

Cargo功能

大多数功能启用相应的通道类。默认启用的stdcrate功能,除了启用stdmpsc通道类外,还通过::std::borrow::ToOwned特征启用enumizer_to_ownedalloc功能通过使用::alloc::borrow::ToOwned来启用替代的enumizer_to_owned模式(但您需要自己声明extern crate alloc;)。std取代了alloc

另请参阅

依赖关系

~1.2–2.9MB
~59K SLoC