【问题标题】:Expression template implementation in Rust like in boost::yapRust 中的表达式模板实现,如 boost::yap
【发布时间】:2021-11-29 06:14:50
【问题描述】:

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

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

  • 目前是否存在使表达式模板具有转换功能 (请参阅 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,它允许我通过带有闭包的子表达式遍历表达式。一旦返回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&lt;U&gt; 工作(因此也接受 E&lt;E&lt;U&gt;&gt;E&lt;E&lt;E&lt;U&gt;&gt;&gt;...)。这可以为 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);
}

我的 Issue 2/2 不是交易破坏者,但我想知道是否有某种方法可以在没有此行的情况下使代码工作:

impl Transform for u32{}

我认为必须这样做对此类库的用户来说是一种麻烦。问题是,我在为E&lt;U&gt; 实现Transform 时具有特征绑定U: Transform。我觉得不稳定的专业化功能在这里可能会有所帮助,但如果可以使用稳定的 Rust 来完成,那就太棒了。

这里是rust playground link

编辑:

如果其他人对此感到困惑,这里有一个rust playground link,它实现了已接受答案的解决方案。它还清理了上面代码中的一些小错误。

【问题讨论】:

    标签: generics rust expression-templates


    【解决方案1】:

    在我看来,这就像 Visitor pattern

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

    与您的尝试不同的是,U 类型在 Box&lt;U&gt; 中放置在堆上(想想 C++ 中的 unique_ptr&lt;U&gt;),这使得整个 E 大小固定(独立于具体的 U)。

    除了让编译器高兴地统一 F 和 Transform 的类型以便它可以同时与 E 和 U 一起工作之外,F 的 trait 参数可能需要装箱以及 Box&lt;dyn Transform&gt; (这大致类似于将 transform 标记为 C++ 中的虚拟方法,以便能够通过知道如何在运行时找到特定 impl 的“智能”指针调用它)。使用 match 解压 x: U 后,应该可以从 Box::&lt;dyn Transform&gt;::new(x) 生成 Box::&lt;dyn Transform&gt;::new(x),因为 U : Transform。有了那个F就会接受它。

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

    【讨论】:

    • 甜!!我正要放弃这个,但拳击U 成功了,see here
    • 不错。这是一个正确的链接吗?它看起来像我的原始代码
    • 哦,对不起。这是正确的:play.rust-lang.org/… 所以我的第二个问题实际上也被这个解决了,因为如果我将Transform trait 设为通用而不是U,我实际上并不需要 trait bound U: Transform
    猜你喜欢
    • 2012-12-10
    • 2013-12-05
    • 2015-09-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多