【发布时间】:2021-11-29 06:14:50
【问题描述】:
我正在尝试自学 Rust,作为一个具有挑战性的学习项目,我想复制 C++ 表达式模板库 boost::yap 的设计模式。我不想要一个完整的实现,我只是想要一个小型演示者来了解 Rust 的泛型是否足够强大以实现它并在此过程中学到一些东西。
我想出了一个主意,但目前卡住了。我的问题是双重的:
- 目前是否存在使表达式模板具有转换功能 (请参阅 boost::yap 或我下面的代码)在 Rust 中不可能实现的原则障碍?
- 如果没有,我怎样才能让它工作?
这是我到目前为止的想法。
我有一个枚举 E,它代表所有支持的操作。在实践中,它将采用两个通用参数来表示任何二元运算的左侧和右侧表达式,并且将具有称为Add、Mul、Sub 等的变体。我将为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: 我想传递一个闭包,它可以在 Self 和 U 上为 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);
}
我的 Issue 2/2 不是交易破坏者,但我想知道是否有某种方法可以在没有此行的情况下使代码工作:
impl Transform for u32{}
我认为必须这样做对此类库的用户来说是一种麻烦。问题是,我在为E<U> 实现Transform 时具有特征绑定U: Transform。我觉得不稳定的专业化功能在这里可能会有所帮助,但如果可以使用稳定的 Rust 来完成,那就太棒了。
编辑:
如果其他人对此感到困惑,这里有一个rust playground link,它实现了已接受答案的解决方案。它还清理了上面代码中的一些小错误。
【问题讨论】:
标签: generics rust expression-templates