[英]How to implement an introspection of a trait content?
我實現了一個小型庫,通過逐步修改計划來逐步進行計算。 我想允許在不修改庫本身的情況下對計划進行自省。 例如,我需要實現一個 function,它在每個執行步驟后打印下一個計划。 或者我可能需要將計划轉換為另一種表示。
庫的中心抽象是一個Plan<T, R>
特征,它輸入一個參數T
並計算R
:
pub trait Plan<T, R> {
fn step(self: Box<Self>, arg: T) -> StepResult<R>;
}
計划返回StepResult<R>
是立即結果或從()
到R
的新計划:
pub enum StepResult<R> {
Plan(Box<dyn Plan<(), R>>),
Result(R),
}
最后我有幾個具體的計划,例如這兩個:
pub struct OperatorPlan<T, R> {
operator: Box<dyn FnOnce(T) -> StepResult<R>>,
}
impl<T, R> Plan<T, R> for OperatorPlan<T, R> {
fn step(self: Box<Self>, arg: T) -> StepResult<R> {
(self.operator)(arg)
}
}
pub struct SequencePlan<T1, T2, R> {
first: Box<dyn Plan<T1, T2>>,
second: Box<dyn Plan<T2, R>>,
}
impl<T1: 'static, T2: 'static, R: 'static> Plan<T1, R> for SequencePlan<T1, T2, R> {
fn step(self: Box<Self>, arg: T1) -> StepResult<R> {
match self.first.step(arg) {
StepResult::Plan(next) => StepResult::plan(SequencePlan{
first: next,
second: self.second,
}),
StepResult::Result(result) => StepResult::plan(ApplyPlan{
arg: result,
plan: self.second,
}),
}
}
}
使用這些計划,我可以組合運算符並從T
逐步計算R
逐步構建計划。
我已閱讀如何創建異構對象集合的答案? 但是“特征”和“枚舉”解決方案對我都不起作用。
我可以像fmt
一樣添加新的 function 或每次convert
為Plan<T, R>
特征,但目標是提供單個 function 以允許在不修改庫本身的情況下進行自省。
我不能將所有計划類型列為枚舉,因為其中一些(如SequencePlan
)是 generics ,因此OperatorPlan
可以返回一個預先不知道確切類型的Plan
。
我嘗試通過添加新特征Visitor<T, R>
和將其接受到Plan<T, R>
特征中的方法來實現訪問者模式:
pub trait Plan<T, R> {
fn step(self: Box<Self>, arg: T) -> StepResult<R>;
fn accept(&self, visitor: &Box<dyn PlanVisitor<T, R>>);
}
trait PlanVisitor<T, R> {
fn visit_operator(&mut self, plan: &OperatorPlan<T, R>);
// Compilation error!
//fn visit_sequence<T2>(&mut self, plan: &SequencePlan<T, T2, R>);
}
這不會編譯,因為訪問SequencePlan
的 function 是由附加類型參數化的。 另一方面,我不需要知道Plan
的完整類型來打印它。
在 C++ 中,我可以使用dynamic_cast<Display>
查看Plan
是否可打印,然后使用指向Display
接口的指針。 我知道 Rust 不支持開箱即用的向下轉換。
我想知道在 Rust 中實現這種自省的自然方法是什么?
操場上更完整的代碼
首先,Rust 不是動態語言,所以編譯后的反省是不可能的,除非你准備好了。 基本上,您必須以某種方式修改您的計划類型和庫以支持外部自省。
選項是您將所有字段公開為公共,以便您可以從外部板條箱 function 對它們進行 go:
pub SequencePlan {
pub first: ...,
pub second: ...,
或者,您在庫中有一個類似訪問者的特征,可以為您遍歷結構,並且您可以從外部獲取結構的所有詳細信息。
您可以擁有自己的訪客,但擁有通用訪客並不是一項基本任務。 您需要為所有 Plan 子類型確定一個常見的 output 類型,並且該類型需要涵蓋所有可能的用例。
在 Rust 中,一般情況自省是在通常用於序列化/轉換的庫serde中完成的。
PS 可以在 Rust 中執行類似dynamic_cast
的操作,但它會是不安全的,因此這通常不是您想要做的。
我在這里發布了我在@battlmonstr 回答后最終編寫的代碼。
Plan 使用erased_serde::Serialize
作為超特征,但serde::Serialize
也需要實現,因為具體計划包含dyn Plan
trait 對象:
use serde::{Serialize, Serializer};
// Generic plan infrastructure
pub trait Plan<T, R>: erased_serde::Serialize {
fn step(self: Box<Self>, arg: T) -> StepResult<R>;
}
impl<T, R: Serialize> Serialize for dyn Plan<T, R> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
erased_serde::serialize(self, serializer)
}
}
在大多數情況下,可以使用#[derive(Serialize)]
來派生實現。 Plan
實現需要對類型參數進行Serialize
:
/// Sequence plan concatenates two underlying plans via middle value of T2 type.
#[derive(Serialize)]
pub struct SequencePlan<T1, T2, R> {
first: Box<dyn Plan<T1, T2>>,
second: Box<dyn Plan<T2, R>>,
}
impl<T1: 'static + Serialize, T2: 'static + Serialize, R: 'static + Serialize> Plan<T1, R> for SequencePlan<T1, T2, R> {
fn step(self: Box<Self>, arg: T1) -> StepResult<R> {
match self.first.step(arg) {
StepResult::Plan(next) => StepResult::plan(SequencePlan{
first: next,
second: self.second,
}),
StepResult::Result(result) => StepResult::plan(ApplyPlan{
arg: result,
plan: self.second,
}),
}
}
}
有時需要自定義實現:
/// Operator from T to StepResult<R> is a plan which calls itself when executed
pub struct OperatorPlan<T, R> {
operator: Box<dyn FnOnce(T) -> StepResult<R>>,
descr: String,
}
impl<T: Serialize, R: Serialize> Plan<T, R> for OperatorPlan<T, R> {
fn step(self: Box<Self>, arg: T) -> StepResult<R> {
(self.operator)(arg)
}
}
impl<T: Serialize, R: Serialize> Serialize for OperatorPlan<T, R> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(self.descr.as_str())
}
}
最后,可以編寫一種方法將計划序列化為字符串:
fn plan_to_string<R>(step: &Box<dyn Plan<(), R>>) -> String {
let mut buf = Vec::new();
let json = &mut serde_json::Serializer::new(Box::new(&mut buf));
let serializer = &mut <dyn erased_serde::Serializer>::erase(json);
step.erased_serialize(serializer).unwrap();
std::str::from_utf8(buf.as_slice()).unwrap().to_string()
}
完整代碼在操場上
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.