#dynamic-dispatch #dispatch #object-oriented #multiple #dyn #multimethod

double-dyn

实现具有多个动态参数调用的函数的宏

2个版本

0.1.1 2022年3月11日
0.1.0 2022年3月11日

#486 in 过程宏

MIT/Apache

74KB
1.5K SLoC

提供在运行时实现具有多个动态参数调用的函数的宏。

![double_dyn!] 宏将定义指定的特质,并为所有提供类型生成实现,然后生成调用适当实现的函数。

用法

在您的 Cargo.toml 中

[dependencies]
double-dyn = "0.1.1"

基本用法

double_dyn! 宏调用包含3个部分。

  1. A和B特质的名称,以及任何子特质界限
  2. 函数原型
  3. 类型对的实现,形式为 <A, B>

示例

use double_dyn::double_dyn;

double_dyn!{
    type A: MyTraitA;
    type B: MyTraitB: std::fmt::Display;

    fn multiply(a: &dyn MyTraitA, b: &dyn MyTraitB) -> Box<dyn MyTraitB>;

    impl for <i32, String>
    {
        fn multiply(a: &i32, b: &String) -> Box<dyn MyTraitB> {
            let multiplied_val = *a * b.parse::<i32>().unwrap();
            Box::new(multiplied_val.to_string())
        }
    }

    impl for <[i8, i16, i32, i64, i128], [f32, f64]>
    {
        fn multiply(a: &#A, b: &#B) -> Box<dyn MyTraitB> {
            Box::new((*a as #B) * *b)
        }
    }
}

let val = multiply(&2, &7.5);
assert_eq!(format!("{}", val), "15");

上述宏调用将定义 MyTraitAMyTraitB 特质,并为所有相关类型提供实现。

如上所示,可以在方括号中指定多个 A 和/或 B 类型。

您可以在实现块中显式使用具体类型,或者使用 #A#B 标记作为函数签名和实现体内的别名,它们将在编译时被它们所代表的类型替换。

# use double_dyn::double_dyn;
double_dyn!{
    type A: MyTrait: std::fmt::Display;
    type B: MyTrait;

    fn multiply(a: &dyn MyTrait, b: &dyn MyTrait) -> Box<dyn MyTrait>;

    #[commutative]
    impl for <[i8, i16, i32, i64, i128], [f32, f64]>
    {
        fn multiply(a: &#A, b: &#B) -> Box<dyn MyTrait> {
            Box::new((*a as #B) * *b)
        }
    }
}

let val = multiply(&7.0, &2);
assert_eq!(format!("{}", val), "14");

可以为A和B提供相同的特质。在实现中,A和B的参数可能仍然是不同的类型。宏将尝试从使用 #A#B 标记来推断哪个参数是 A 以及哪个是 B,但如果存在歧义,则假定第一个 &dyn MyTrait 参数是 A

使用 #[commutative] 属性会在 A 被替换为 B 以及反之时生成额外的实现。

如果 AB 特性相同,则 A 特性的界限将优先。

你可以在同一个 double_dyn 宏调用中声明多个函数,并且所有函数将使用相同的特性。然而,每个声明的函数都必须在每个 impl 块中实现。

更多的使用示例可以在这里的测试中找到。

限制

  • 所有的 impls 必须在同一个 double_dyn 宏调用中,包括定义。我希望能够支持将声明与实现分开,并允许根据需要添加额外的 impls,但我没有一种稳健的方法来在每次宏调用之间进行通信。这被 这个问题 所阻塞。

  • 每个 double_dyn 宏调用定义一个特性或一对特性。此宏不是用来向现有特性添加方法的。你可以使用这个宏来定义一个特性,然后让这个特性成为你定义的另一个特性的超特性,从而允许在特性上使用双-dyn 方法。但是,由于稳定编译器中缺少 特性向上转换,这仍然是一个限制。如果你有关于如何让添加方法到现有特性更好的想法,请与我联系。

  • 函数不能有泛型参数。这是基于函数被转换成特性方法的根本限制,而特性需要保持对象安全。

  • impl 不支持泛型“通用实现”。A 类型永远不能支持泛型类型,原因如上;对象安全性禁止在特性方法中使用泛型。B 类型理论上可以支持通用实现,但目前该宏不能解析 impl 中的 where 子句。如果你认为这个功能很重要,请告诉我,我可以添加它。

  • 例如,可见性限定符(如 pub),必须对每个函数原型相同。可见性将应用于所有生成的特性和函数。

  • 不支持传递自有参数。例如,一个参数必须是 &dyn ATrait 的形式,而不是 Box<dyn ATrait>

  • 可能会报告多次错误和警告。

未来愿景

我希望能够通过 impl 块添加新的函数实现,这些块不是原始调用的一部分。换句话说,允许函数签名部分在代码中,并允许在其他地方添加额外的实现。不幸的是,我不认为这是可能的,因为 Rust 没有能力在宏调用之间进行通信。这在此处进行了讨论

我还希望为在现有特性上实现方法提供更多灵活性。请参阅上面的 限制 部分。我对您认为有用的建议持开放态度。

致谢

本软件包实现了由@h2co3此帖中提出的策略。

通过学习@dtolnay的代码,我学会了如何编写proc宏,并从seq-macro软件包中借用了某些实用函数。

依赖关系

~120KB