【问题标题】:Using enums for dynamic polymorphism in Rust在 Rust 中使用枚举实现动态多态性
【发布时间】:2020-09-11 13:51:12
【问题描述】:

如果已经知道需要动态多态性的代码中涉及的所有有限数量的类型,则与使用 Box 相比,使用 enum 的性能会更好,因为后者使用动态内存分配,您需要使用具有虚函数调用的特征对象。

也就是说,与使用 std::variantstd::visit 的 C++ 中的等效代码相比,在这种情况下,Rust 似乎涉及更多样板代码,至少对我来说是这样(我还没有学会使用过程宏)。在这里举个例子:我有一堆struct 类型:

struct A {
    // ...
}

struct B {
    // ...
}

// ...

struct Z {
    // ...
}

它们都实现了 AlphabetLetter 特征,即:

trait AlphabetLetter {
    fn some_function(&self);
}

由于涉及的类型集是已知且有限的,我想使用enum

enum Letter {
    AVariant(A),
    BVariant(B),
    // ...
    ZVariant(Z),
}

这里我们已经有了第一个样板:我需要为涉及的每个类型变体的enum 值添加一个名称。但真正的问题是:enumLetter 本身就是一个AlphabetLetter,它只是表示我们在运行时不知道它是哪个字母的事实。所以我开始为它实现 trait:

impl AlphabetLetter for Letter {
    fn some_function(&self) {
        match self {
            Letter::AVariant(letter) => letter.some_function();
            Letter::BVariant(letter) => letter.some_function();
            // ...
            Letter::ZVariant(letter) => letter.some_function();
        }
    }
}

是的,这很容易变成很多代码,但我没有找到其他方法。在 C++ 中,多亏了泛型 lambda,人们可以只使用 std::visitstd::variant,而且它是单行的。如果不手动为 enum 中的每个变体中的特征 X 中的每个函数编写所有模式匹配,我怎么能做到这一点?

【问题讨论】:

  • 没有多态性也没有动态调度,因为类型是静态已知的。
  • IIUC,C++ 模板是无类型的,使得std::visit 在功能上类似于使用 Rust 宏(请注意,C 宏完全是另一回事)。在 C++ 中为这种便利性付出的代价是在使用时报告类型错误而不是声明,并且在 lambda 中执行不兼容的操作的错误可能是……不透明的。
  • (另请注意,过程宏和声明性宏是不同类型的宏;声明性宏更容易,并且足以使这种事情工作而无需太多重复。)
  • 我认为最好的解决方案是本地特征对象once they become available。这将消除对动态内存分配的需求,在这种情况下,我认为虚函数调用与您的 match 语句非常相似(并且可能更快)。

标签: enums rust variant run-time-polymorphism


【解决方案1】:

您可以通过示例使用宏(而不是过程宏)来避免样板:

macro_rules! make_alphabet {
    ($($x:ident),*) => {
        enum Letter {
            $(
                $x($x),
            )*
        }

        impl AlphabetLetter for Letter {
            fn some_function(&self) {
                match self {
                    $(
                        Letter::$x(letter) => letter.some_function(),
                    )*
                }
            }
        }
    };
}

然后你调用它来生成一切:

make_alphabet!(A, B, C, ..., Z);

现在您可以随时访问它,只要您有letter: Letter

letter.some_function();

对于不需要对所有变体进行操作的方法,可以在外面加上impl

【讨论】:

    【解决方案2】:

    polymorphic_enum 宏生成一个具有所选名称和变体的枚举,以及另一个具有所选名称的宏。 这个生成的宏特定于生成的枚举,因为它为所有变体重复相同的代码块(类似于闭包)(正是您明确所做的)。 它假设所有变体都可以以完全相同的方式使用;因此得名polymorphic_enum

    您不必为要以这种方式处理的每个枚举编写新宏 因为生成了特定于每个特定枚举的宏。 您甚至不必在枚举上实现该特征(欢迎回来鸭式输入;^),但如果您愿意,您可以。 你只需要以一种不常见的方式声明你的枚举......

    应该是多态的代码的调用类似于 向std::visit() 提供通用 lambda 闭包时会做什么 C++ 中的单个 std::variant(但是这里没有多个调度)。

    trait AlphabetLetter {
        fn some_function(&self) -> String;
        fn something_else(
            &self,
            arg: usize,
        ) {
            println!("--> {}, arg={}", self.some_function(), arg);
        }
    }
    
    struct A {
        // ...
    }
    
    struct B {
        // ...
    }
    
    // ...
    
    struct Z {
        // ...
    }
    
    impl AlphabetLetter for A {
        fn some_function(&self) -> String {
            format!("some function on A")
        }
    }
    
    impl AlphabetLetter for B {
        fn some_function(&self) -> String {
            format!("some function on B")
        }
    }
    
    // ...
    
    impl AlphabetLetter for Z {
        fn some_function(&self) -> String {
            format!("some function on Z")
        }
    }
    
    macro_rules! polymorphic_enum {
        ($name:ident $macro:ident, $($variant:ident($type:path),)*) => {
            enum $name { $($variant($type)),* }
            macro_rules! $macro {
                ($on:expr, |$with:ident| $body:block) => {
                    match $on {
                        $($name::$variant($with) => $body )*
                    }
                }
            }
        }
    }
    
    polymorphic_enum! {
        Letter use_Letter,
        AVariant(A),
        BVariant(B),
        // ...
        ZVariant(Z),
    }
    
    fn main() {
        let letters = vec![
            Letter::AVariant(A {}),
            Letter::BVariant(B {}),
            // ...
            Letter::ZVariant(Z {}),
        ];
        for (i, l) in letters.iter().enumerate() {
            let msg = use_Letter!(l, |v| { v.some_function() });
            println!("msg={}", msg);
            use_Letter!(l, |v| {
                let msg = v.some_function();
                v.something_else((i + 1) * msg.len())
            });
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-03-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-18
      • 2011-10-27
      • 2023-02-15
      相关资源
      最近更新 更多