使用接受闭包的方法在 Rust 中创建对象安全特征

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

我想为 Map 创建一个具有以下定义的特征:

pub trait Map<K: Sync, V> {
    fn put(&mut self, k: K, v: V) -> Option<V>;
    fn upsert<U: Fn(&mut V)>(&self, key: K, value: V, updater: &U);
    fn get<Q: ?Sized>(&self, k: &Q) -> Option<V> where K: Borrow<Q>, Q: Eq + Hash + Sync;
    // other methods ommited for brevity
}

现在的问题是,如果我实现这个特征,例如MyHashMap,那么我就不能有这样的表达式:

let map: Box<Map<i32, i32>> = Box::new(MyHashMap::<i32, i32>::new());

错误将是:

特质

map::Map
无法制作成物体

如何解决这个问题?因为直接开始使用 Map 实现并不是一个好主意,因为它不是一个好的软件工程实践。

主要问题是特征中的 getupsert 方法接受泛型类型参数。我的第一次尝试是摆脱这些泛型类型参数。

对于 get 方法来说,这是可能的,尽管它偏离了 Rust 集合中 get 的常见签名,并且使其使用场景更加有限。这是结果:

pub trait Map<K: Sync, V> {
    fn put(&mut self, k: K, v: V) -> Option<V>;
    fn upsert<U: Fn(&mut V)>(&self, key: K, value: V, updater: &U);
    fn get(&self, k: &K) -> Option<V>;
    // other methods ommited for brevity
}

但是,我不知道如何在 upsert 中删除泛型类型参数。

关于如何处理这个问题有什么想法吗?

generics rust
2个回答
3
投票

如何解决这个问题?因为直接开始使用 Map 实现并不是一个好主意,因为它不是一个好的软件工程实践。

这在 Java 中是很好的做法,但在其他语言中却不一定。例如,在动态类型语言中,如果所有

Map
实现对方法使用相同的命名约定,则无需进行大量代码更改即可替换它们。

在像 Rust 这样具有良好类型推断的语言中,您通常不需要用过多的类型注释来污染代码。因此,如果您需要更改具体类型,需要更新的地方就会减少,并且比 Java 等语言中的问题要少。

“好”Java 有一个隐含的目标,即您可能希望在运行时交换抽象类型的任何实现。 Java 使这很容易做到,因此这样做是合理的,尽管在实践中很少需要这样做。更有可能的是,您将使用一些需要抽象类型的代码,并为它提供一个在编译时已知的具体实例。

这正是 Rust 处理参数的方式。当您指定

M: Map
参数时,您可以使用 any
M
,它也实现了
Map
。但是编译器会在编译时找出您实际使用的具体实现(这称为单态化)。如果需要更改具体实现,只需更改一行代码即可。这对于性能也有巨大的好处。

那么,回到你的第一个问题:

如何解决这个问题?

如果你真的想这样做,你可以为映射器函数引入另一个特征对象。特征对象不能拥有带有自己的泛型参数的方法的原因是,编译器在编译时无法知道该对象的大小。因此,只需将您的函数参数也放入特征对象中,这样这个问题就消失了:

fn upsert(&self, key: K, value: V, updater: &Fn(&mut V));

但正如我上面所描述的,我真正的答案是让事情变得简单。如果您确实需要这个

Map
抽象,那么它应该可以与在编译时已知实例化的类型参数完美配合。当在编译时无法知道具体类型时,例如在运行时可以更改实现时,请使用特征对象。


2
投票

免责声明:我发现前提(良好实践)有问题,但仍然认为这个问题值得回答。运行时多态性有其一席之地,特别是可以减少编译时间。

创建特征的对象安全版本是完全可能的,它只需要两个组件:

  • 您希望通过运行时多态性使用的方法不应具有泛型类型参数,
  • 具有类型参数的方法(并且不能通过运行时多态性使用)应该通过
    where Self: Sized
    子句进行保护。

可以提供此类方法的两种替代方案,尽管在 Rust 中它需要不同的名称:

pub trait Map<K: Sync, V> {
    fn put(&mut self, k: K, v: V) -> Option<V>;

    fn upsert_erased(&self, key: K, value: V, updater: &dyn Fn(&mut V));

    fn upsert<U: Fn(&mut V)>(&self, key: K, value: V, updater: &U)
        where Self: Sized
    {
        self.upsert_erased(key, value, updater);
    }
}

这不是我在这里选择通过

upsert
提供
upsert_erased
的默认实现,它减少了具体类型必须实现的方法数量,同时仍然提供了在性能保证的情况下实际实现它的可能性。

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