繁体   English   中英

如何实现对特征内容的自省?

[英]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 或每次convertPlan<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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM