[英]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.