2 个版本
0.1.1 | 2022年4月17日 |
---|---|
0.1.0 | 2022年3月16日 |
#9 in #inherent
24 monthly downloads
在 trait-enumizer 中使用
54KB
1.5K SLoC
trait-enumizer
从 trait 生成枚举,并提供它们之间的转换器。
特性
- 基于指定的 trait(或固有实现)生成枚举,其中每个变体对应一个方法。参数对应于变体的字段;返回值对应于某个通道的发送者。
- 生成具有适当
match
的函数调用函数,该match
在内部生成。这允许枚举“应用于”实现该 trait 的对象。 - 生成一个代理,允许使用原始 trait 的方法调用(如果可能)或类似原始的 API 获取枚举值序列。代理还帮助处理“通道化”返回值。
- 处理返回值。
- 处理异步(仅固有实现)。
该库可以用作同步机制,或作为构建 actor 或远程过程调用的构建块。
库的主要部分是 enumizer
属性宏。它应该附加到 trait 或固有实现。它将输入 trait 或实现复制到 proc macro 输出(不包括伪 derive-helpers 属性),然后还生成以下项
- 代表方法的枚举
- 零个或多个“调用函数”
- 零个或多个“代理结构体”
参数
#[trait_enumizer::enumizer(...)]
接受以下参数
name=<ident>
- 设置枚举名称。必需。pub
,pub_crate
- 将所有生成的项标记为pub
或pub(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>
- 内置方法的名称。必需。ref
或ref_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()
参数时,会生成代理。它们使用以下子参数
Fn
、FnMut
、FnOnce
- 设置代理将携带的闭包的类型。必需。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
。
代理是一个具有公共字段的泛型元组结构。该字段应实现Fn
、FnMut
或FnOnce
。如果指定了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代替。
simple_derive.rs
- 简单演示。simple_manual.rs
- 上面的演示的扩展实现。mutable_derive.rs
、mutable_manual.rs
、move_derive.rs
、move_manual.rs
- 同样的内容,但用于mut和once的情况。mixed.rs
- 展示了一些其他功能,还使用了线程来演示如何与通道交互(以flume为例)。还展示了将自定义derive注入到生成的枚举中。returnval_derive.rs
、returnval_manual_generic.rs
- 处理返回值的技巧模式。returnval_manual_flume.rs
- 原始版本,没有通道抽象,为flume硬编码。rpc.rs
- 展示了如何使用serde_json和flume进行远程过程调用的高级用法,自定义returnval=
类。两个主要Flume通道模拟套接字,中间Flume通道将返回值路由回调用者。toowned_manual
、toowned_derive
- 扩展(手动)和自动生成的#[enumizer_to_owned]
功能演示。inherent_derive
- 展示了inherent_impl
模式。async_derive.rs
、async_manual.rs
、async_returnval_derive.rs
、async_returnval_manual.rs
、async_rpc.rs
、async_rpc.rs
- 上面的某些测试的异步版本。channelclasses_showcase.rs
- 各种内置通道类。
另请参阅
依赖项
~1.5MB
~36K SLoC