如何解决 Rust 中的对象安全特征

问题描述 投票:0回答:1

我正在用 Rust 编写一个玩具数据库实现,但我无法绕过这个对象安全的特征要求。我基本上想要一种用 Rust 表示具有不同类型列的数据库表的方法。 这就是我当前现有代码的样子。

trait Primative {}
impl Primative for i32 {}

struct Table {
    value: HashMap<String, Arc<dyn ColType>>,
}

trait ColType {
    fn get_data(&self) -> &Vec<impl Primative>;
}

struct TableValue<T: Primative> {
    data: Vec<T>
}

impl <T: Primative> ColType for TableValue<T> {
    fn get_data(&self) -> &Vec<T> {
        return &self.data;
    }
}

这是我在数据存储方面取得的进展的简化版本。话虽如此,这段代码给出了警告

ColType
无法制作成对象。基本上我只想编译这个或一些类似的解决方案。要求是:

a)我需要 Primative 作为一个特征(在 i32 等上实现)。这是因为我不想使用枚举,因为我不确定如何确保每个值都是相同的变体以及开销。

b)我不想做任何

Vec<Box<impl Primative>>
的事情,因为我相信应该有办法不做那么糟糕的事情。 (当 T: Primative 工作的 Vec 时,为什么我要为每个项目创建一个新指针!)

c)解决方案实际上必须能够存储内容(通过向

HashMap<String, Arc<dyn ColType<dyn Primative>>>
添加通用参数,我得到了一些接近于使用
ColType<T: Primative>
的东西,但这最终导致了无法满足的特征边界。

在我看来,我应该能够返回一些类型为

Vec<impl ColType + Sized>
的堆分配 vec,但这可能只是我。

另外,对于任何拼写错误,我深表歉意。我在手机上写这篇文章是因为堆栈溢出网站无法在 librewolf 上运行。

如果这只是一个技能问题,请告诉我。

rust struct traits
1个回答
0
投票

在特征方法中返回

&Vec<impl Primative>
会使特征失去对象安全性,因为编译器无法知道堆分配项的布局或对齐方式,因此无法保证运行时的内存安全。这也让我困惑了好几次,因为
dyn Trait
符号让我根据里氏替换原则进行思考,但它实际上是关于指针以及我们指向 CPU 的位置。
dyn Trait
类型是胖指针;它存储一个指向对象的指针(在本例中,是实现
Trait
的类型)和指向 vtable 的指针。后者保存有关类型的各种信息,例如在哪里可以找到某些方法的机器代码等。这非常酷,并且允许动态调度;在运行时,vtable 用于将 CPU 指向可以找到我们要调用的方法的正确位置。

另一方面,编译器需要知道

get_data
ColType
特征的所有实现者返回的确切类型,这对于 impl Primative 来说是不可能的。如果它不知道返回的确切类型,它就无法编译用于动态分派的胖指针,因为它不知道 vtable 指向哪里。

我可能会解释得很糟糕,所以我会推荐 Jon Gjengset 的书 Rust for Rustaceans,它详细介绍了这个主题(我上周刚读过这一章,这就是我回答的原因这个问题)。

确实,使用

Box
可以解决这个问题,但这会带来运行时性能损失。您说过您不想使用枚举,因为开销(?)和“我不确定如何确保每个值都是相同的变体”,但这不正是 Rust 的闪光点吗?我的意思是,模式匹配是最酷的事情之一(以我的愚见;))。

enum Primative {
    Int(i32),
    // Add more types here if needed, e.g., Float(f64)
}

struct Table {
    value: HashMap<String, Arc<dyn ColType>>,
}

trait ColType {
    fn get_data(&self) -> &Vec<Primative>;
}

struct TableValue {
    data: Vec<Primative>,
}

impl ColType for TableValue {
    fn get_data(&self) -> &Vec<Primative> {
        &self.data
    }
}

现在,编译器知道

ColType::get_data()
特征方法的返回值的确切类型,因为它们始终是
Primative
类型。唯一的缺点是内存使用:
&Vec<Primative>
中的每个项目都与
Primative
的最大可能变体一样大。

© www.soinside.com 2019 - 2024. All rights reserved.