用一种接受封闭的方法在Rust中创建一个对象安全特征



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

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纳入对象

一个人如何解决这个问题?因为这不是一个好的软件工程实践,因为这不是一个好主意,因为它不是一个好的软件工程实践。

主要问题是 get upSert 在特质中接受通用类型参数中的方法。我的第一个尝试是摆脱这些通用类型参数。

对于 get 方法,即使它偏离了Rust Collections中的 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 中的通用类型参数的方法不了解。

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

一个人如何解决这个问题?因为这不是一个好的软件工程实践,因为这不是一个好主意,因为它不是一个好的软件工程实践。

这是Java的好习惯,但不一定在其他语言中。例如,在动态键入的语言中,只要所有Map实现都使用相同的命名约定,则可以在没有大量代码更改的情况下替换它们。

在像Rust这样的语言中,具有良好的类型推理,通常不需要用过多的类型注释来污染代码。结果,如果您需要更改混凝土类型,则需要更新的地方更少,而且它比Java等语言中发现的问题要少。

"好" Java具有一个隐含的目标,即您可能想在运行时间上交换任何抽象类型的实现。Java使此操作容易做到,因此,即使在实践中,这很少需要这样做,这是合理的。更有可能,您将使用一些期望抽象类型的代码,并为其提供一个具体实例,该实例已知 compile time

这正是Rust与参数一起工作的方式。指定M: Map参数时,您可以使用 M,也可以实现Map。但是编译器将在编译时弄清楚您实际使用的哪种具体实现(这称为单态化)。如果您需要更改具体实现,则可以通过仅更改一行代码来做到这一点。这对性能也有很大的好处。

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

一个人如何解决这个问题?

如果您真的想这样做,则可以为映射器函数介绍另一个特征对象。特质对象无法具有具有自己的通用参数的方法的原因是,编译器在编译时不知道会到达那里的任何内容。因此,只要将您的函数参数也引入特征对象,以便此问题消失:

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

,但我真正的答案是,如上所述,要使事情变得简单。如果您确实需要此Map抽象,则应与在编译时已知的实例化的类型参数完美搭配。使用特质对象,以便在编译时无法知道混凝土类型的何时可以在运行时更改实现。

免责声明:我发现前提(良好的实践)有缺陷,但仍然认为值得回答的问题。运行时多态性有其位置,特别是减少汇编时间。

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

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

可以提供这种方法的两种选择,尽管在生锈中需要不同的名称:

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_erased提供upsert的默认实现,它减少了混凝土类型必须实现的方法数量,同时仍提供了实际实施的可能性。

<</p>

最新更新