提问人:MysteryMoose 提问时间:11/1/2023 更新时间:11/1/2023 访问量:52
在 Rust 中为泛型类型实现类型安全的工厂方法
Implementing Type-Safe Factory Methods for Generic Types in Rust
问:
这里是 Rust 的新手,尽管我在低级/内核驱动程序/嵌入式 C 上花了相当多的时间,并且对 C++11 和更新版本有一些少量经验。我怀疑我的部分问题将是忘记旧习惯。
我将如何实现一个类型安全的方法,该方法来构造实现给定特征但本身需要泛型参数的对象?
这个项目是一个简单的 MQTT 到 Prometheus 的桥接;这里的目标是能够解析一个配置文件,然后调用每个配置的项目来返回一个对象,MQTT 端稍后可以在发布时使用该对象来更新值(通过特征)。考虑(为简洁起见进行了修剪):register_metric
UpdateFromString
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 隐藏在应用程序的其余部分,我认为这可能是问题的一部分。如果我完全静态地(即)这样做,这是非常微不足道的,但需要将原始 Prometheus 类型泄漏到配置解析逻辑中。它也不仅限于 Prometheus 类型。fn register_metric<T>(&self, name: String, desc: String) -> Box<impl UpdateFromString>
另一种方法是使用动态调度,这是我上面尝试的。也就是说,我不确定如何声明正确的类型以使编译器满意。不知不觉中,我在编译时不知道最终的存储大小似乎很不高兴。这SO 帖子似乎表明这应该有效,并且作为 C 的类型安全版本对我的 C 大脑有意义。不幸的是,这仍然在编译时给出未知的大小错误;这是有道理的,因为 Box 至少具有内部类型的大小,现在是可变的。std::any::Any
Box<Any>
struct foo myvar = *((struct foo *)malloc(sizeof struct foo))
AFAICT,即使是 / 也不起作用,因为该签名必须是类似的东西,它只会将整个不同大小的类型事物更深一层。Rc
Arc
Rc<Box<foo>>
那么,如上所述,构造和返回实现给定特征并接受泛型参数的受支持类型列表之一的类型安全方法是什么?
最后的一些说明:
- Prometheus 在注册新指标时需要使用 in。
Box
- 该对象是在 中创建的;使用 是 Prometheus 示例中的复制粘贴(尽管我不完全理解为什么这是必要的)。
registry
new
Box.clone()
- 如果可能的话,我真的很想这样做。
unsafe
- 如果可能的话,我宁愿不使用外部板条箱;实施“艰难的方式”对我来说更有启发性。
答:
您想要的是类型擦除。你通过返回一个存在类型来部分地实现它,但要一路走到那里,你必须使用一个动态调度的对象,在某种间接的后面。幸运的是,您已经以 .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
}
评论
expected 'Box<GenericCounter<AtomicU64>>', found 'Box<GenericGauge<AtomicI64>>
GenericCounter
GenericGauge
int
float
match
dyn
dyn
Box::new(... as dyn _)
let metric: Box<dyn UpdateFromString> = match {...
评论
cargo check