#traits #enums #channel #inherent #proxy #enumizer #trait-enumizer

无 std trait-enumizer-derive

为 trait-enumizer 提供辅助库

2 个版本

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

#9 in #inherent

24 monthly downloads
trait-enumizer 中使用

MIT/Apache

54KB
1.5K SLoC

trait-enumizer

img

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

特性

  • 基于指定的 trait(或固有实现)生成枚举,其中每个变体对应一个方法。参数对应于变体的字段;返回值对应于某个通道的发送者。
  • 生成具有适当 match 的函数调用函数,该 match 在内部生成。这允许枚举“应用于”实现该 trait 的对象。
  • 生成一个代理,允许使用原始 trait 的方法调用(如果可能)或类似原始的 API 获取枚举值序列。代理还帮助处理“通道化”返回值。
  • 处理返回值。
  • 处理异步(仅固有实现)。

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

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

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

参数

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

  • name=<ident> - 设置枚举名称。必需。
  • pubpub_crate - 将所有生成的项标记为 pubpub(crate) 分别
  • returnval=<macro_class_name> - 启用更复杂的模式,其中返回值被处理。影响其他生成的项目(call_fns 和代理)的 API。输入宏用作临时 GAT 特性,具有特殊化。有关更多信息,请参阅专门的 README 部分。
  • enum_attr - 将自定义属性(例如 enum_attr[derive(serde_derive::Serialize)])注入枚举声明中。可重复使用。您需要使用方括号来执行此操作。
  • inherent_impl - 在 inherent impl 上基于枚举,而不是 trait。
  • 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。使用来自 returnval 宏类的 send_async 伪方法代替 send
  • extra_arg_type(<type>) - 向 try_call 函数添加额外参数。该参数将出现在所有 macro_class_name!(send(...)) 回调中。

这些函数用于将枚举值“转换为”方法调用。调用函数作为生成枚举的内建 impl 函数生成。第一个参数是 self。第二个参数是您指定的 trait 的实现者(或引用)的值。如果指定了 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> - 生成“resultified”特质,而不是固有的实现try_*函数。
  • 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_*方法异步。您可以要求Enumizer生成“resultified”特质,代理然后实现它(当然,不包括async)。async还会影响returnval宏的使用。

您还可以让Enumizer代理实现原始特质(除非是async)。为此有两种策略:不可失败(如果未使用返回值,并且您的Fn通过使用std::convert::Infallible)来放弃错误处理)和展开。

您可以创建非异步原始方法的异步代理,反之亦然。

示例(简化版)

#[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=flume_class(您需要导入它:use 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的额外字段(一个硬编码的标识符)。此字段的类型由通道类控制,并可能取决于返回值的类型。所有与此额外字段相关的交互都通过通道类的伪方法进行。

测试(也作为文档)

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

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

另请参阅

依赖项

~1.5MB
~36K SLoC