6

当一个人已经知道某些需要动态多态性的代码中涉及的所有有限数量的类型时,enum与 using 相比, using 可以更好地提高性能,Box因为后者使用动态内存分配,并且您需要使用具有虚函数调用的 trait 对象作为出色地。

也就是说,与使用std::variantand的 C++ 中的等效代码相比,std::visit在这种情况下,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 Letteris 本身是一个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::visitastd::variant并且它是一个单行。如果不手动为特征 X 中的每个变体中的每个函数编写所有模式匹配,我该如何做同样的事情enum

4

2 回答 2

4

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

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外部。

于 2020-09-11T14:47:44.403 回答
2

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

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

应该是多态的代码的调用类似于在 C++std::visit()中为单个提供通用 lambda 闭包时所做的操作(但是这里没有多重分派)。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())
        });
    }
}
于 2020-09-11T15:22:17.003 回答