我正在嘗試自學 Rust,作為一個具有挑戰性的學習專案,我想復制 C 運算式模板庫boost::yap的設計模式。我不想要一個完整的實作,我只想要一個小的演示者來了解 Rust 的泛型是否足夠強大以實作它并在此程序中學到一些東西。
我提出了一個想法,但目前陷入困境。我的問題是雙重的:
- 目前是否有一個原則障礙使具有轉換功能的 運算式模板(參見 boost::yap 或我下面的代碼)在 Rust 中變得不可能?
- 如果沒有,我怎樣才能使它作業?
這是我到目前為止所提出的。
我有一個E代表所有支持的操作的列舉。在實踐中,它會采取較任何二進制運算的左側和右側的運算式兩個通用引數,并且會對變形叫Add,Mul,Sub等等。我會std::ops::{Add, Mul, Sub}為E<U>.
然而,出于演示目的,讓我們假設我們只有兩個變體,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)
}
}
接下來,我實作了一個 trait 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,)>
|
This is my Issue 1/2: I want to pass in a closure that can work on both Self and on U for E<U> (and thus accepts also E<E<U>> and E<E<E<U>>>...). Can this be done for generic types in Rust? Or if my approach is wrong, what's the right way of doing this? In C i would use SFINAE or if constexpr.
Here is a little test for the expression template library, to see how this can be used:
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);
}
My Issue 2/2 is not a deal breaker, but I am wondering if there is some way that I can make the code work without this line:
impl Transform for u32{}
我認為必須這樣做對這樣一個圖書館的用戶來說是一種麻煩。問題是,我U: Transform對Transformfor的實作有限制E<U>。我覺得不穩定的專業化特性可能會在這里有所幫助,但如果這可以用穩定的 Rust 來完成,那就太棒了。
這是Rust 游樂場鏈接。
編輯:
如果其他人對此感到困惑,這里是一個rust playground 鏈接,它實作了已接受答案的解決方案。它還清理了上面代碼中的一些小錯誤。
uj5u.com熱心網友回復:
這對我來說看起來像一個訪問者模式。
此處的解決方案與您嘗試執行的操作類似。
與您嘗試的不同之處在于 U 型別被放置在堆中Box<U>(unique_ptr<U>在 C 中考慮),這使得整個 E 大小固定(獨立于具體的 U)。
除了讓編譯器樂于統一 F 和 Transform 的型別以便它可以同時處理 E 和 U 之外,F 的 trait 引數可能也需要裝箱Box<dyn Transform>(這大致類似于標記transform作為 C 中的虛擬方法,以便能夠通過“智能”指標呼叫它,該指標知道如何在運行時找到特定的實作)。x: U使用 match解壓后,應該可以Box::<dyn Transform>::new(x)從U : Transform. 有了那個 F 會接受它。
如果沒有 Box,我認為唯一的其他解決方案是使用宏,并為每種型別顯式生成方法。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/370269.html
下一篇:泛型和在運行時選擇正確的介面實作
