2017-09-15 79 views
1

我想创建一个映射一个特征,定义如下的方法创建锈对象安全的特质:与接受封闭

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实现并不是一个好主意,因为这不是一个好的软件工程实践。

主要问题是得到upsert方法在trait中接受泛型类型参数。我的第一个尝试是摆脱这些泛型类型参数。

对于得到方法,这是可能的,即使它从共同特征得到生锈收藏偏离并使其使用方案较为有限。下面是结果:

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过程中的任何想法。

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

+3

*因为直接开始使用Map实现并不是一个好主意,因为它不是一个好的软件工程实践。* =>您需要更新您的工程实践。在Rust或C++等本地语言中,使用具体实例(和泛型)而不是接口可以使编译器挤出最后一盎司的性能。 –

回答

3

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

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

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

“好”Java有一个隐含的目标,您可能想要在运行时之间交换抽象类型的任何实现。 Java使得这很容易做到,所以这样做是合理的,尽管在实践中这很少需要。更有可能的是,您将使用一些需要抽象类型的代码,并且您可以在编译时处提供一个具体实例,该编译器在处已知。

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

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

如何才能解决这个问题呢?

如果你真的想这样做,你可以引入另一特征对象的映射函数。 trait对象不能拥有自己的泛型参数的原因是因为编译器在编译时无法知道将会出现的大小。因此,只要你的函数参数为特征的对象太多,所以这个问题消失:

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

但我真正的答案是,正如我上面描述,让事情变得简单。如果你确实需要这个抽象,那么它应该在编译时已知的实例化类型参数完美地工作。在编译时无法知道具体类型时使用trait对象,例如实现可以在运行时更改的地方。

2

声明:我发现前提(良好做法)有缺陷,但仍然认为值得回答的问题。运行时多态性具有它的地位,特别是减少编译时间。

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

  • ,你希望通过运行时多态性的使用不应该有泛型类型参数的方法,
  • 应该通过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一个默认的实现,它减少了具体类型必须实施的方法数量,同时仍然提供在性能保证的情况下实际执行该方法的可能性。