簡體   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