繁体   English   中英

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

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

主要问题是特征中的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中删除通用类型参数的方法一无所知

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

如何解决这个问题? 因为直接开始使用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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM