在Rust中为泛型类型实现类型安全工厂方法

xpcnnkqh  于 2023-11-19  发布在  其他
关注(0)|答案(1)|浏览(108)

我是Rust的新手,虽然我在低级/内核驱动程序/嵌入式C上花了很多时间,并且对C++11和更新版本有一些简单的经验。我怀疑我的部分问题将是不学习旧习惯。
我如何实现一个类型安全的方法来构造实现给予特征但自身需要泛型参数的对象?
这个项目是一个简单的MQTT到Prometheus的桥梁;这里的目标是能够解析配置文件,然后在每个配置项上调用register_metric以返回一个对象,MQTT端可以稍后在发布时使用该对象来更新值(通过UpdateFromString trait)。考虑(为简洁起见进行了修剪):

enum MetricType { IntegerCounter, FloatCounter, IntegerGauge, FloatGauge }

pub trait UpdateFromString {
    fn update_from_string(&self, value: String);
}

impl UpdateFromString for prometheus::Counter {
    fn update_from_string(&self, value: String) {
        self.inc();
    }
}

// snip - impl UpdateFromString for other Prometheus types we support

struct PrometheusEndpoint { ... }

impl PrometheusEndpoint
{
    fn new(runtime: std::rc::Rc<tokio::runtime::Runtime>) -> Result<PrometheusEndpoint, i32> { ... }

    async fn metrics_handler(registry: std::sync::Arc<prometheus::Registry>) -> Result<impl warp::Reply, std::convert::Infallible> { ... }

    fn register_metric(&self, name: String, desc: String, mtype: MetricType) -> Box<impl UpdateFromString>
    {
        let metric: Box<dyn std::any::Any>;

        // Instantiate raw metric type
        match mtype {
            MetricType::IntegerCounter => metric = Box::new(prometheus::IntCounter::new(name, desc).unwrap()),
            MetricType::IntegerGauge   => metric = Box::new(prometheus::IntGauge::new(name, desc).unwrap()),
            MetricType::FloatCounter   => metric = Box::new(prometheus::Counter::new(name, desc).unwrap()),
            MetricType::FloatGauge     => metric = Box::new(prometheus::Gauge::new(name, desc).unwrap()),
            _ => panic!("Unknown metric type"),
        };

        // Register metric with registry
        // NOTE: After this completes, this metric can potentially be scraped by the remote Prometheus instance
        self.registry.register(metric.clone()).unwrap();

        // We implement UpateFromString on the Prometheus base types above so we can just return them directly.
        // This is safe since Rust won't let us use any functions not specified via our trait so a container
        // object is just redundant.
        metric
    }
}

字符串
我定义了自己的输入枚举来隐藏Prometheus,我认为这可能是问题的一部分。如果我完全静态地这样做(即fn register_metric<T>(&self, name: String, desc: String) -> Box<impl UpdateFromString>),这是相当微不足道的,但需要将原始的Prometheus类型泄漏到配置解析逻辑。它也没有限制我只能使用Prometheus类型。
另一种方法是使用动态分派,这就是我上面尝试的。也就是说,我不确定如何声明适当的类型来让编译器满意。不幸的是,它似乎不高兴我不知道编译时的最终存储大小。this SO post似乎表明std::any::Any应该工作,Box<Any>对我的C脑来说是有意义的类型-C的struct foo myvar = *((struct foo *)malloc(sizeof struct foo))的安全版本。不幸的是,这仍然会在编译时产生未知大小的错误;这是有意义的,因为Box将至少具有现在可变的内部类型的大小。
当然,即使是Rc/Arc也不行,因为签名必须是像Rc<Box<foo>>这样的东西,这只是把整个不同大小的类型加深了一层。
所以,如上所述,什么是类型安全的方式来构造和返回一个支持的类型列表,实现给定的trait * 和 * 接受泛型参数?
一些最后的笔记:

  • 注册新指标时,Prometheus需要使用Box
  • registry对象是在new中创建的; Box.clone()的使用是从Prometheus示例中复制过来的(尽管我不完全理解为什么有必要这样做)。
  • 如果可能的话,我真的很想在没有unsafe的情况下做这件事。
  • 如果可能的话,我宁愿不使用外部板条箱;实施“艰难的方式”对我来说更有指导意义。
ttisahbt

ttisahbt1#

你想要的是 * 类型擦除 *。你通过返回一个impl Trait存在类型来部分实现它,但是要达到这个目的,你必须使用一个动态分派的对象dyn Trait,在某种间接性后面。幸运的是,你已经有了Box的形式。
下面是我建议您的register_metric应该是这样的:

fn register_metric(&self, name: String, desc: String, mtype: MetricType) -> Box<dyn UpdateFromString>
    {
        // Instantiate raw metric type
        let metric = match mtype {
            MetricType::IntegerCounter => Box::new(prometheus::IntCounter::new(name, desc).unwrap()),
            MetricType::IntegerGauge   => Box::new(prometheus::IntGauge::new(name, desc).unwrap()),
            MetricType::FloatCounter   => Box::new(prometheus::Counter::new(name, desc).unwrap()),
            MetricType::FloatGauge     => Box::new(prometheus::Gauge::new(name, desc).unwrap()),
            _ => panic!("Unknown metric type"),
        };

        // Register metric with registry
        // NOTE: After this completes, this metric can potentially be scraped by the remote Prometheus instance
        self.registry.register(metric.clone()).unwrap();

        // We implement UpateFromString on the Prometheus base types above so we can just return them directly.
        // This is safe since Rust won't let us use any functions not specified via our trait so a container
        // object is just redundant.
        metric
    }

字符串

相关问题