2个版本
0.1.1 | 2022年6月14日 |
---|---|
0.1.0 | 2022年6月12日 |
#545 in 过程宏
66KB
742 行
描述
invoke_impl是一个基于属性过程宏的Rust库crate,当应用于结构的impl块时,会在编译前插入额外代码,生成可以自动调用impl块中关联函数和方法的函数,前提是它们具有相同的签名。
工作原理
假设我们有一个以下的Rust结构和相关的impl块
struct Tester1;
#[invoke_impl]
impl Tester1 {
pub fn fn1(i: i32) -> i32 {
i
}
pub fn fn2(i: i32) -> i32 {
i
}
pub fn fn3(i: i32) -> i32 {
i
}
}
当前情况下,当将#[invoke_impl]属性过程宏应用于Tester1的impl块时,以下代码会在编译前生成(可以使用cargo expand命令查看)
struct Tester1;
impl Tester1 {
pub fn fn1(i: i32) -> i32 {
i
}
pub fn fn2(i: i32) -> i32 {
i
}
pub fn fn3(i: i32) -> i32 {
i
}
pub fn invoke_all(i: i32, mut consumer: impl FnMut(i32)) {
consumer(Tester1::fn1(i));
consumer(Tester1::fn2(i));
consumer(Tester1::fn3(i));
}
pub fn invoke_subset(
i: i32,
mut consumer: impl FnMut(i32),
mut invoke_impl_iter: impl Iterator<Item = usize>,
) {
for invoke_impl_i in invoke_impl_iter {
match invoke_impl_i {
0usize => consumer(Tester1::fn1(i)),
1usize => consumer(Tester1::fn2(i)),
2usize => consumer(Tester1::fn3(i)),
_ => ::core::panicking::panic_fmt(::core::fmt::Arguments::new_v1(
&["Iter contains invalid function index!"],
&[],
)),
}
}
}
pub fn invoke_all_enumerated(i: i32, mut consumer: impl FnMut(usize, i32)) {
consumer(0usize, Tester1::fn1(i));
consumer(1usize, Tester1::fn2(i));
consumer(2usize, Tester1::fn3(i));
}
pub fn invoke_all_enum(i: i32, mut consumer: impl FnMut(Tester1_invoke_impl_enum, i32)) {
consumer(Tester1_invoke_impl_enum::fn1, Tester1::fn1(i));
consumer(Tester1_invoke_impl_enum::fn2, Tester1::fn2(i));
consumer(Tester1_invoke_impl_enum::fn3, Tester1::fn3(i));
}
pub fn invoke_enumerated(
i: i32,
mut consumer: impl FnMut(usize, i32),
mut invoke_impl_iter: impl Iterator<Item = usize>,
) {
for invoke_impl_i in invoke_impl_iter {
match invoke_impl_i {
0usize => {
consumer(0usize, Tester1::fn1(i));
}
1usize => {
consumer(1usize, Tester1::fn2(i));
}
2usize => {
consumer(2usize, Tester1::fn3(i));
}
_ => ::core::panicking::panic_fmt(::core::fmt::Arguments::new_v1(
&["Iter contains invalid function index!"],
&[],
)),
}
}
}
pub fn invoke_enum(
i: i32,
mut consumer: impl FnMut(Tester1_invoke_impl_enum, i32),
mut invoke_impl_iter: impl Iterator<Item = Tester1_invoke_impl_enum>,
) {
for invoke_impl_i in invoke_impl_iter {
match invoke_impl_i {
Tester1_invoke_impl_enum::fn1 => {
consumer(Tester1_invoke_impl_enum::fn1, Tester1::fn1(i));
}
Tester1_invoke_impl_enum::fn2 => {
consumer(Tester1_invoke_impl_enum::fn2, Tester1::fn2(i));
}
Tester1_invoke_impl_enum::fn3 => {
consumer(Tester1_invoke_impl_enum::fn3, Tester1::fn3(i));
}
}
}
}
pub const METHOD_COUNT: usize = 3usize;
pub const METHOD_LIST: [&'static str; 3usize] = ["fn1", "fn2", "fn3"];
}
#[derive(Debug, Clone, Copy)]
pub enum Tester1_invoke_impl_enum {
fn1,
fn2,
fn3,
}
impl TryFrom<&str> for Tester1_invoke_impl_enum {
type Error = &'static str;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
"fn1" => Ok(Self::fn1),
"fn2" => Ok(Self::fn2),
"fn3" => Ok(Self::fn3),
_ => Err("Input str does not match any enums in Self!"),
}
}
}
impl From<Tester1_invoke_impl_enum> for &str {
fn from(en: Tester1_invoke_impl_enum) -> Self {
use Tester1_invoke_impl_enum::*;
match en {
fn1 => "fn1",
fn2 => "fn2",
fn3 => "fn3",
}
}
}
如所示,添加到impl块中的invoke函数通过FnMut(函数返回类型)闭包处理被调用关联函数的输出。如果关联函数没有返回类型,invoke函数将没有闭包参数或有一个只接受指定类型(usize或宏生成的枚举类型)的闭包,以指示调用了哪个函数。具体来说,如果被调用的函数没有返回类型,invoke_all和invoke_subset将不接受任何闭包,invoke_all_enum和invoke_enum将接受一个接受枚举类型的闭包,该枚举类型由宏生成,而invoke_all_enumerated和invoke_enumerated将接受一个接受usize类型的闭包。
invoke_impl属性还可以接受两个用户指定的参数。名称参数必须是一个字符串字面量,提供为#[invoke_impl(name("MY_NAME"))]。当使用此参数时,名称参数会被附加,为所有生成的代码提供不同的标识符。
struct Tester1;
#[invoke_impl(name("MY_NAME"))]
impl Tester1 {
pub fn fn1(i: i32) -> i32 {
i
}
pub fn fn2(i: i32) -> i32 {
i
}
pub fn fn3(i: i32) -> i32 {
i
}
}
变为
struct Tester1;
impl Tester1 {
pub fn fn1(i: i32) -> i32 {
i
}
pub fn fn2(i: i32) -> i32 {
i
}
pub fn fn3(i: i32) -> i32 {
i
}
pub fn invoke_all_MY_NAME(i: i32, mut consumer: impl FnMut(i32)) {
consumer(Tester1::fn1(i));
consumer(Tester1::fn2(i));
consumer(Tester1::fn3(i));
}
pub fn invoke_subset_MY_NAME(
i: i32,
mut consumer: impl FnMut(i32),
mut invoke_impl_iter: impl Iterator<Item = usize>,
) {
for invoke_impl_i in invoke_impl_iter {
match invoke_impl_i {
0usize => consumer(Tester1::fn1(i)),
1usize => consumer(Tester1::fn2(i)),
2usize => consumer(Tester1::fn3(i)),
_ => ::core::panicking::panic_fmt(::core::fmt::Arguments::new_v1(
&["Iter contains invalid function index!"],
&[],
)),
}
}
}
pub fn invoke_all_enumerated_MY_NAME(i: i32, mut consumer: impl FnMut(usize, i32)) {
consumer(0usize, Tester1::fn1(i));
consumer(1usize, Tester1::fn2(i));
consumer(2usize, Tester1::fn3(i));
}
pub fn invoke_all_enum_MY_NAME(
i: i32,
mut consumer: impl FnMut(Tester1_invoke_impl_enum_MY_NAME, i32),
) {
consumer(Tester1_invoke_impl_enum_MY_NAME::fn1, Tester1::fn1(i));
consumer(Tester1_invoke_impl_enum_MY_NAME::fn2, Tester1::fn2(i));
consumer(Tester1_invoke_impl_enum_MY_NAME::fn3, Tester1::fn3(i));
}
pub fn invoke_enumerated_MY_NAME(
i: i32,
mut consumer: impl FnMut(usize, i32),
mut invoke_impl_iter: impl Iterator<Item = usize>,
) {
for invoke_impl_i in invoke_impl_iter {
match invoke_impl_i {
0usize => {
consumer(0usize, Tester1::fn1(i));
}
1usize => {
consumer(1usize, Tester1::fn2(i));
}
2usize => {
consumer(2usize, Tester1::fn3(i));
}
_ => ::core::panicking::panic_fmt(::core::fmt::Arguments::new_v1(
&["Iter contains invalid function index!"],
&[],
)),
}
}
}
pub fn invoke_enum_MY_NAME(
i: i32,
mut consumer: impl FnMut(Tester1_invoke_impl_enum_MY_NAME, i32),
mut invoke_impl_iter: impl Iterator<Item = Tester1_invoke_impl_enum_MY_NAME>,
) {
for invoke_impl_i in invoke_impl_iter {
match invoke_impl_i {
Tester1_invoke_impl_enum_MY_NAME::fn1 => {
consumer(Tester1_invoke_impl_enum_MY_NAME::fn1, Tester1::fn1(i));
}
Tester1_invoke_impl_enum_MY_NAME::fn2 => {
consumer(Tester1_invoke_impl_enum_MY_NAME::fn2, Tester1::fn2(i));
}
Tester1_invoke_impl_enum_MY_NAME::fn3 => {
consumer(Tester1_invoke_impl_enum_MY_NAME::fn3, Tester1::fn3(i));
}
}
}
}
pub const METHOD_COUNT_MY_NAME: usize = 3usize;
pub const METHOD_LIST_MY_NAME: [&'static str; 3usize] = ["fn1", "fn2", "fn3"];
}
#[derive(Debug, Clone, Copy)]
pub enum Tester1_invoke_impl_enum_MY_NAME {
fn1,
fn2,
fn3,
}
impl TryFrom<&str> for Tester1_invoke_impl_enum_MY_NAME {
type Error = &'static str;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
"fn1" => Ok(Self::fn1),
"fn2" => Ok(Self::fn2),
"fn3" => Ok(Self::fn3),
_ => Err("Input str does not match any enums in Self!"),
}
}
}
impl From<Tester1_invoke_impl_enum_MY_NAME> for &str {
fn from(en: Tester1_invoke_impl_enum_MY_NAME) -> Self {
use Tester1_invoke_impl_enum_MY_NAME::*;
match en {
fn1 => "fn1",
fn2 => "fn2",
fn3 => "fn3",
}
}
}
invoke_impl 可以接受的另一个参数是 clone 参数。由于过程宏基本上只能在标记上工作,因此 invoke_impl 宏无法判断它从一个 invoke 函数转发到相关函数或方法调用中的参数是否是移动类型。因此,参数标识符被简单地复制粘贴到相关的调用中。这对于像 usize 这样的可复制类型或者有时可以隐式借用如 &mut (某物) 这样的类型来说是可以的,但是对于像 String 这样的移动类型来说就失败了。为了处理这种情况,有两种主要的选择:要么让 impl 块中的相关函数/方法将它们的参数作为复制类型(即引用)接受,要么在每个调用时克隆输入。clone 参数是后一种方法。该参数接受一个逗号分隔的整数字面量列表,表示关联函数的哪些参数(从 0 开始索引)应该在每次调用之前被克隆。
struct Tester1;
#[invoke_impl(clone(1))]
impl Tester1 {
pub fn fn1(i: i32, s: String) -> i32 {
i
}
pub fn fn2(i: i32, s: String) -> i32 {
i
}
pub fn fn3(i: i32, s: String) -> i32 {
i
}
}
变为
struct Tester1;
impl Tester1 {
pub fn fn1(i: i32, s: String) -> i32 {
i
}
pub fn fn2(i: i32, s: String) -> i32 {
i
}
pub fn fn3(i: i32, s: String) -> i32 {
i
}
pub fn invoke_all(i: i32, s: String, mut consumer: impl FnMut(i32)) {
consumer(Tester1::fn1(i, s.clone()));
consumer(Tester1::fn2(i, s.clone()));
consumer(Tester1::fn3(i, s.clone()));
}
pub fn invoke_subset(
i: i32,
s: String,
mut consumer: impl FnMut(i32),
mut invoke_impl_iter: impl Iterator<Item = usize>,
) {
for invoke_impl_i in invoke_impl_iter {
match invoke_impl_i {
0usize => consumer(Tester1::fn1(i, s.clone())),
1usize => consumer(Tester1::fn2(i, s.clone())),
2usize => consumer(Tester1::fn3(i, s.clone())),
_ => ::core::panicking::panic_fmt(::core::fmt::Arguments::new_v1(
&["Iter contains invalid function index!"],
&[],
)),
}
}
}
pub fn invoke_all_enumerated(i: i32, s: String, mut consumer: impl FnMut(usize, i32)) {
consumer(0usize, Tester1::fn1(i, s.clone()));
consumer(1usize, Tester1::fn2(i, s.clone()));
consumer(2usize, Tester1::fn3(i, s.clone()));
}
pub fn invoke_all_enum(
i: i32,
s: String,
mut consumer: impl FnMut(Tester1_invoke_impl_enum, i32),
) {
consumer(Tester1_invoke_impl_enum::fn1, Tester1::fn1(i, s.clone()));
consumer(Tester1_invoke_impl_enum::fn2, Tester1::fn2(i, s.clone()));
consumer(Tester1_invoke_impl_enum::fn3, Tester1::fn3(i, s.clone()));
}
pub fn invoke_enumerated(
i: i32,
s: String,
mut consumer: impl FnMut(usize, i32),
mut invoke_impl_iter: impl Iterator<Item = usize>,
) {
for invoke_impl_i in invoke_impl_iter {
match invoke_impl_i {
0usize => {
consumer(0usize, Tester1::fn1(i, s.clone()));
}
1usize => {
consumer(1usize, Tester1::fn2(i, s.clone()));
}
2usize => {
consumer(2usize, Tester1::fn3(i, s.clone()));
}
_ => ::core::panicking::panic_fmt(::core::fmt::Arguments::new_v1(
&["Iter contains invalid function index!"],
&[],
)),
}
}
}
pub fn invoke_enum(
i: i32,
s: String,
mut consumer: impl FnMut(Tester1_invoke_impl_enum, i32),
mut invoke_impl_iter: impl Iterator<Item = Tester1_invoke_impl_enum>,
) {
for invoke_impl_i in invoke_impl_iter {
match invoke_impl_i {
Tester1_invoke_impl_enum::fn1 => {
consumer(Tester1_invoke_impl_enum::fn1, Tester1::fn1(i, s.clone()));
}
Tester1_invoke_impl_enum::fn2 => {
consumer(Tester1_invoke_impl_enum::fn2, Tester1::fn2(i, s.clone()));
}
Tester1_invoke_impl_enum::fn3 => {
consumer(Tester1_invoke_impl_enum::fn3, Tester1::fn3(i, s.clone()));
}
}
}
}
pub const METHOD_COUNT: usize = 3usize;
pub const METHOD_LIST: [&'static str; 3usize] = ["fn1", "fn2", "fn3"];
}
#[derive(Debug, Clone, Copy)]
pub enum Tester1_invoke_impl_enum {
fn1,
fn2,
fn3,
}
impl TryFrom<&str> for Tester1_invoke_impl_enum {
type Error = &'static str;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
"fn1" => Ok(Self::fn1),
"fn2" => Ok(Self::fn2),
"fn3" => Ok(Self::fn3),
_ => Err("Input str does not match any enums in Self!"),
}
}
}
impl From<Tester1_invoke_impl_enum> for &str {
fn from(en: Tester1_invoke_impl_enum) -> Self {
use Tester1_invoke_impl_enum::*;
match en {
fn1 => "fn1",
fn2 => "fn2",
fn3 => "fn3",
}
}
}
请注意,为了减少这些已经相当长的示例的总长度,我已经删除了从生成的枚举中生成的 #[derive()] 代码,但在使用 cargo expand 时,它将在实践中可见。
用例
这个 crate 的主要用例很明显:当用户希望调用具有相同签名的许多函数时,通常是为了对结果进行某种操作。与解决此问题的其他方法相比,这种方法具有几个优点。首先,执行类似行为的一种方式是存储一个函数指针的 Vec,或者可能是boxed闭包的Vec。然而,这两种方法都需要手动将这些项添加到 Vec 中,或者使用另一个过程宏。此外,这两种技术不允许存储泛型函数,而不需要用具体类型实例化,这会增加开发者负责维护的代码量。
相比之下,当使用这种方法时,当在 impl 块中实现时,函数会自动添加到 invoke 函数和相关常量中。此外,如果 impl 块中的函数是泛型的,那么生成的 invoke 函数也将是泛型的
struct Tester4;
#[invoke_impl]
impl Tester4 {
pub fn fn1<T: Add + Copy>(i: T, j: T) -> <T as Add>::Output {
i + j
}
pub fn fn2<T: Add + Copy>(i: T, j: T) -> <T as Add>::Output {
i + j
}
pub fn fn3<T: Add + Copy>(i: T, j: T) -> <T as Add>::Output {
i + j
}
}
变为
struct Tester4;
impl Tester4 {
pub fn fn1<T: Add + Copy>(i: T, j: T) -> <T as Add>::Output {
i + j
}
pub fn fn2<T: Add + Copy>(i: T, j: T) -> <T as Add>::Output {
i + j
}
pub fn fn3<T: Add + Copy>(i: T, j: T) -> <T as Add>::Output {
i + j
}
pub fn invoke_all<T: Add + Copy>(i: T, j: T, mut consumer: impl FnMut(<T as Add>::Output)) {
consumer(Tester4::fn1::<T>(i, j));
consumer(Tester4::fn2::<T>(i, j));
consumer(Tester4::fn3::<T>(i, j));
}
pub const METHOD_COUNT: usize = 3usize;
pub const METHOD_LIST: [&'static str; 3usize] = ["fn1", "fn2", "fn3"];
}
总的来说,这种方法似乎更容易维护(当事情顺利的时候...)。
请注意,当存在泛型类型参数时,将自动应用 turbofish 语法。在上面的例子中,T 可以通过查看传递给函数的字段推导出来,所以这不太重要,但在下面的例子中就很重要
struct Tester5;
#[invoke_impl]
impl Tester5 {
pub fn fn1<C: FromIterator<usize>>(i: &Vec<usize>) -> C {
i.iter().copied().collect::<C>()
}
pub fn fn2<C: FromIterator<usize>>(i: &Vec<usize>) -> C {
i.iter().copied().collect::<C>()
}
pub fn fn3<C: FromIterator<usize>>(i: &Vec<usize>) -> C {
i.iter().copied().collect::<C>()
}
}
由于 C 不能从函数接收到的参数中推断出来,因此 invoke_all 等调用函数的实现必须(并且确实)使用 turbofish 为每个调用指定 C 的类型
pub fn invoke_all<C: FromIterator<usize>>(i: &Vec<usize>, mut consumer: impl FnMut(C)) {
consumer(Tester5::fn1::<C>(i));
consumer(Tester5::fn2::<C>(i));
consumer(Tester5::fn3::<C>(i));
}
请注意,由于这个问题 issue,任何具有 impl trait 使用的函数目前都无法使用此宏,因为 invoke 函数定义中存在 turbofish。
当前状态
目前,invoke 函数从 impl 块中的第一个方法/函数的签名继承其可见性。现在它们适用于实际方法,这些方法将 &self 或 &mut self 作为参数(如何处理以 self 作为参数的方法是另一回事;我可能最终会通过 clone 来实现它)。此外,错误输出基本上是垃圾,因为我已经专注于尝试为大多数情况得到一个工作的宏,而牺牲了良好的错误消息;产生的错误消息将通过恐慌来实现。
计划进行的改进
我计划扩展此宏,以便用户可以在宏中指定为创建的函数指定不同的名称,为指定的函数签名生成多个此类函数等。此外,我希望能编写处理 impl Trait 参数和返回类型的案例处理。所有这些都将很快实现,希望如此!
依赖关系
~1.5MB
~35K SLoC