Rust 中的表达式模板实现,如 boost::yap

Expression template implementation in Rust like in boost::yap

我正在尝试自学 Rust,作为一个具有挑战性的学习项目,我想复制 C++ 表达式模板库的设计模式 boost::yap。我不想要一个完整的实现,我只想要一个小型演示程序来了解 Rust 的泛型是否足够强大以实现它并在此过程中学到一些东西。

我想出了一个主意,但目前卡住了。我的问题是双重的:

这是我到目前为止的想法。

我有一个枚举 E 代表所有支持的操作。实际上,它会采用两个通用参数来表示任何二元运算的左侧和右侧表达式,并且会有称为 AddMulSub 等的变体。我会为 E<U>.

实现特征 std::ops::{Add, Mul, Sub}

然而,出于演示目的,我们假设我们只有两个变体,Terminal 表示一个包含值的表达式,而 Neg 是目前唯一受支持的一元运算。

use std::ops::Neg;

enum E<U> {
    Terminal(U),
    Neg(U)
}

impl<U> Neg for E<U> {
    type Output = E<E<U>>;
    fn neg(self) -> Self::Output {
        E::Neg(self)
    }
}

接下来,我实现了一个特性 Transform,它让我可以通过带有闭包的子表达式来遍历表达式。一旦returns Some(_),闭包将停止递归。这就是我想出的 (代码无法编译):

trait Transform<Arg = Self> {

    fn transform<R,F>(&self, _f: F) -> Option<R>
    where F: FnMut(&Arg) -> Option<R> 
    {
        None
    }
}

impl<U> Transform for E<U> 
where U : Transform<U> + Neg
{
    fn transform<R,F>(&self, mut f: F) -> Option<R>
    where F: FnMut(&Self) -> Option<R>
    {
        // CASE 1/3: Match! return f(self)
        if let Some(v) = f(self) { return Some(v); };

        match self {
            E::Terminal(_) => None, // CASE 2/3: We have reached a leaf-expression, no match!
            E::Neg(x) => {      // CASE 3/3: Recurse and apply operation to result
                x.transform(f).map(|y| -y) // <- error[E0277]: expected a `FnMut<(&U,)>` closure, found `F`
            }
        }
    }
}

这是编译器错误:

error[E0277]: expected a `FnMut<(&U,)>` closure, found `F`
  --> src/main.rs:36:29
   |
36 |                 x.transform(f).map(|y| -y) // <- error[E0277]: expected a `Fn<(&U,)>` closure, found `F`
   |                             ^ expected an `FnMut<(&U,)>` closure, found `F`
   |
help: consider further restricting this bound
   |
28 |     where F: FnMut(&Self) -> Option<R> + for<'r> std::ops::FnMut<(&'r U,)>
   |                                        +++++++++++++++++++++++++++++++++++

这是我的 问题 1/2: 我想传递一个闭包,它可以在 SelfU 上工作,因为 E<U>(因此也接受 E<E<U>>E<E<E<U>>>...)。可以对 Rust 中的泛型类型这样做吗?或者,如果我的方法是错误的,那么正确的做法是什么?在 C++ 中,我会使用 SFINAE 或 if constexpr.

下面是对表达式模板库的小测试,看看如何使用它:

fn main() {
    //This is needed, because of the trait bound `U: Transform` for `Transform`
    //Seems like an unnecessary burden on the user...
    impl Transform for i32{}

    // An expression template 
    let y = E::Neg(E::Neg(E::Neg(E::Terminal(42))));

    // A transform that counts the number of nestings
    let mut count = 0;
    y.transform(|x| {
        match x {
            E::Neg(_) => {
                count+=1;
                None
            }
            _ => Some(()) // must return something. It doesn't matter what here.
        }
    });
    assert_eq!(count, 3);
    
    // a transform that replaces the terminal in y with E::Terminal(5)
    let expr = y.transform(|x| {
       match x {
           E::Terminal(_) => Some(E::Terminal(5)),
           _ => None
       } 
    }).unwrap();
    
    // a transform that evaluates the expression
    // (note: should be provided as method for E<U>)
    let result = expr.transform(|x| {
        match *x {
            E::Terminal(v) => Some(v),
            _ => None
        }
    }).unwrap();
    assert_eq!(result, -5);
}

我的 问题 2/2 不是一个交易破坏者,但我想知道是否有某种方法可以让代码在没有这一行的情况下工作:

impl Transform for u32{}

我认为必须这样做对于此类库的用户来说是一件令人讨厌的事情。问题是,我在 E<U>Transform 实现上有特征绑定 U: Transform。我觉得不稳定的专业化功能可能会在这里有所帮助,但如果这可以用稳定的 Rust 来完成,那就太棒了。

这里是 rust playground link.

编辑:

如果其他人遇到这个问题,这里有一个 rust playground link 实现了已接受答案的解决方案。它还清除了上面代码中的一些小问题。

我觉得这像 Visitor pattern

解决方案 here 看起来与您正在尝试做的相似。

与你的尝试不同的是,U类型在Box<U>中被放置在堆上(想想C++中的unique_ptr<U>),这使得整个E大小固定(独立于具体的U) .

除了让编译器乐于统一 F 和 Transform 的类型以便它可以同时处理 E 和 U,F 的特征参数可能需要像 Box<dyn Transform>(这大致类似于将 transform 标记为 C++ 中的虚拟方法,以便能够通过知道如何在运行时查找特定实现的“智能”指针调用它)。使用 match 解压缩 x: U 后,自 U : Transform 以来应该可以从中生成 Box::<dyn Transform>::new(x)。这样F就接受了

没有 Box 我认为唯一的其他解决方案是使用宏,并为每种类型显式生成方法。