在 Rust 中寻找更好的枚举方法设计模式

Looking for a better design pattern for methods of enums in Rust

提问人:exocortex 提问时间:1/14/2023 最后编辑:cafce25exocortex 更新时间:1/16/2023 访问量:222

问:

我需要一种方法将不同的对象放在一个枚举中,这些对象都实现了某个特征。该枚举应实现一个以某种方式调用其变体方法的方法,例如多次调用。 我试图举一个非常简单的例子,但它仍然没有我想要的那么短。 更多解释:我想写一个积分某些微分方程的求解器,即计算物理系统在特定时间跨度内的行为。对于每个时间步长,都会调用该方法。但是当我执行程序时,我希望能够在运行时选择使用哪个物理系统。我的想法是有一个包含不同物理系统的枚举,例如 而且(实际上,这可能是一个双钟摆,或者一个振动的弦 - 没关系)。integrate()integrate()integrate()OscillatorAOscillatorB

pub trait Integrate {
    fn integrate(&mut self);
}

pub struct OscillatorA {
    z: u32,
}

impl Integrate for OscillatorA {
    fn integrate(&mut self) {
        self.z += 1; // something happens here
    }
}

#[derive(Debug)]
pub struct OscillatorB {
    x: u32,
    y: u32,
}

impl Integrate for OscillatorB {
    fn integrate(&mut self) {
        self.x += 1; // something different happens here
        self.y += 2;
    }
}

#[derive(Debug)]
pub enum Oscillator {
    A(OscillatorA),
    B(OscillatorB),
    // ... many other physical systems come here
}

impl Oscillator {
    pub fn new(num: &u64) -> Self {
        match num {
            0 => Self::A(OscillatorA { z: 1 }),
            1.. => Self::B(OscillatorB { x: 1, y: 2 }),
        }
    }
}

impl Integrate for Oscillator {
    fn integrate(&mut self) {
        // this looks like it should be redundant:
        match self {
            Self::A(osc) => osc.integrate(),
            Self::B(osc) => osc.integrate(),
        }
    }
}

pub fn integrate_n_times(object: &mut impl Integrate, n: u64) {
    for _ in 0..n {
        object.integrate();
    }
}

fn main() {
    let which = 0; // can be set via commandline arguments.

    let mut s = Oscillator::new(&which);
    integrate_n_times(&mut s, 10);
    // ..
}

该函数将调用 -trait 所需方法的 n 次。但它不知何故感觉不对劲,因为它会在每次迭代中解决一个匹配语句。我想通过编译器优化可以避免这种情况,但它在某种程度上“感觉”不对,因为它肯定是这样读的。 我缺少更好的设计模式吗?我是否也应该通过特征“integrate_n_times”方法?(但随后我会依靠它在每个振荡器结构中正确编写)。integrate_n_times(&mut self, n)integrate()Integrate

我不知何故需要有一个“main-struct”,它包含所有不同的物理系统,并且可以根据我传递给程序的参数来调用它们。

Rust 方法 枚举

评论


答:

1赞 Iggy 1/15/2023 #1

我可能会在这里使用动态调度。虽然它通常比使用静态调度慢,但我认为它比大规模匹配情况更快。另外,我认为它更容易使用,只要我们不尝试使用 Any 和 down-cast 获得原始类型。

impl Oscillator {
    pub fn new(num: &u64) -> Box<dyn Integrate> {
        match num {
            0 => Box::new(OscillatorA { z: 1 }),
            1.. => Box::new(OscillatorB { x: 1, y: 2 }),
        }
    }
}

pub fn integrate_n_times(object: &mut Box<dyn Integrate>, n: u64) {
    for _ in 0..n {
        object.integrate();
    }
}

fn main() {
    let which = 0; // can be set via commandline arguments.
    let mut my_oscillator: Box<dyn Integrate> = Oscillator::new(&which);  
    integrate_n_times(&mut my_oscillator, 10);
}

评论

0赞 exocortex 1/15/2023
我能否以某种方式知道每个单独的函数调用(即对振荡器 A 或振荡器 B 等进行积分)本身都是编译器优化的?每个集成操作都将独立于其他操作进行“调度”。我只需要一种方法将它们全部放在一个容器中(这就是我选择枚举的原因)。谢谢,我会继续阅读!dynBox
0赞 Iggy 1/15/2023
Fyi 是指向堆上对象的指针,该对象保证实现该特征。您可以使用以下命令进行集合: .现在,为了知道你正在调用哪个函数,你有 2 个选项,trait 或 have a on trait。然后,每个实现都将返回正确的枚举变体,然后您必须再次使用 match 来确定该类型。Box dyn IntegrateIntegrateVec<Box<dyn Integrate>>Anyfn which() -> OscillatorIntegrate
0赞 exocortex 1/16/2023
我看了一会儿你的代码,但我仍然不确定有什么区别。每次迭代仍会计算 match 语句。
0赞 Iggy 1/16/2023
@exocortex啊,你是对的!该函数需要返回实现 Integrate 特征的特定类型,而不是枚举。由于他们都实现了它,它仍然工作正常,但正在运行比赛。我已经更新了答案。new
0赞 exocortex 1/16/2023
啊,现在我明白了!谢谢!我刚刚意识到;我还需要这种方式的枚举吗?我想我可以在没有枚举的情况下拥有该函数。Oscillatornew() -> Box< ... >