[英]Create an Object-safe Trait in Rust with a method that accepts a closure
我想使用以下定义为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());
错误将是:
trait
map::Map
无法制作为对象
如何解决这个问题? 因为直接开始使用Map实现不是一个好主意,因为这不是一个好的软件工程实践。
主要问题是特征中的get和upsert方法接受通用类型参数。 我的第一次尝试是摆脱这些通用类型参数。
对于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中删除通用类型参数的方法一无所知 。
关于如何处理此问题的任何想法?
如何解决这个问题? 因为直接开始使用Map实现不是一个好主意,因为这不是一个好的软件工程实践。
这是Java的良好做法,但不一定是其他语言。 例如,在动态类型的语言中,如果所有Map
实现均对方法使用相同的命名约定,则无需大量代码更改即可替换它们。
在具有良好类型推断功能的Rust之类的语言中,通常不需要使用过多的类型标注来污染代码。 结果,如果您需要更改具体类型,则需要更新的地方就更少了,这比在Java之类的语言中遇到的问题少。
“良好” Java的隐含目标是,您可能希望在运行时交换抽象类型的任何实现。 Java使此操作易于实现,因此这样做是合理的,尽管实际上很少需要这样做。 更可能的是,您将使用一些期望抽象类型的代码,并为它提供在编译时已知的具体实例。
这正是Rust使用参数的方式。 指定M: Map
参数时,您可以使用任何也实现Map
M
但是编译器会在编译时确定您实际使用的是哪种具体实现(这称为“同构化”)。 如果您需要更改具体的实现,则只需更改一行代码即可。 这对于性能也有巨大的好处。
因此,回到您的第一个问题:
如何解决这个问题?
如果确实要执行此操作,则可以为mapper函数引入另一个 trait对象。 特质对象不能具有带有其自己的通用参数的方法的原因是,因为编译器在编译时无法知道将要存放的内容的大小。 因此,也只需将您的函数参数设置为特征对象,这样就可以解决此问题:
fn upsert(&self, key: K, value: V, updater: &Fn(&mut V));
但是,如上所述,我的真实答案是保持简单。 如果您确实需要此Map
抽象,则它应与在编译时知道实例化的类型参数一起很好地工作。 在无法在编译时知道具体类型的情况下(例如,在运行时可以更改实现的情况下),请使用trait对象。
免责声明:我认为前提(良好实践)有误,但仍然认为该问题值得回答。 运行时多态性占有一席之地,特别是可以减少编译时间。
创建特征的对象安全版本是完全可能的,它只需要两个组件:
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_erased
提供upsert
的默认实现的upsert_erased
,它减少了具体类型必须实现的方法的数量,同时仍然提供了在性能允许的情况下实际实现它的可能性。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.