将枚举用于Rust中的动态多态性

问题描述

一个人已经知道某些需要动态多态的代码中涉及的所有有限数量的类型时,与使用enum相比,使用Box可能会更好,因为后者使用动态内存分配,并且您'将需要使用具有虚拟函数调用的特征对象。

也就是说,与使用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值的名称。但是真正的问题是:enum Letter本身就是AlphabetLetter,它仅表示我们在运行时不知道它是哪个字母的事实。因此,我开始为其实现特征:

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::visit就可以std::variant了,而且它是一个内衬。不用手动为特征X中的每个变量X enum中的每个变量编写所有模式匹配,该如何做?

解决方法

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

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

,

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())
        });
    }
}