使用接受闭包

时间:2017-09-15 09:14:06

标签: generics rust

我想使用以下定义为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实现不是一个好主意,因为它不是一个好的软件工程实践。

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

对于 get 方法,即使它与生锈集合中的 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 中删除泛型类型参数的方法一无所知。

关于如何处理这个问题的任何想法?

2 个答案:

答案 0 :(得分:3)

  

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

这是Java中的好习惯,但不一定是其他语言。例如,在动态类型语言中,如果所有Map实现对方法使用相同的命名约定,则可以替换它们而无需进行大量代码更改。

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

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

这正是Rust如何使用参数。指定M: Map参数后,您可以使用任何 M来实现Map。但是编译器会在编译时弄清楚你实际使用的具体实现(这称为单形化)。如果需要更改具体实现,可以通过仅更改一行代码来实现。这对性能也有巨大的好处。

所以,回到你的第一个问题:

  

如何解决这个问题?

如果你真的想这样做,可以为mapper函数引入另一个特征对象。特征对象不能拥有带有自己的泛型参数的方法的原因是因为编译器无法在编译时知道将要去那里的任何东西的大小。所以只需将你的函数参数变成一个特征对象,这样就会消失这个问题:

fn upsert(&self, key: K, value: V, updater: &Fn(&mut V));
但是,正如我上面所描述的那样,我的真实答案是保持简单。如果你确实需要这个Map抽象,那么它应该与在编译时已知实例化的类型参数完美配合。在编译时无法知道具体类型时使用特征对象,例如,实现可以在运行时更改。

答案 1 :(得分: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: &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的默认实现,它减少了具体类型必须实现的方法的数量,同时仍提供实际实现它的可能性保证它。