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

Generics 使用接受闭包的方法在Rust中创建对象安全特性,generics,rust,Generics,Rust,我想使用以下定义为Map创建一个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<Q: ?Sized>(&self, k: &

我想使用以下定义为Map创建一个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<Q: ?Sized>(&self, k: &Q) -> Option<V> where K: Borrow<Q>, Q: Eq + Hash + Sync;
    // other methods ommited for brevity
}
但是,我不知道如何删除upsert中的泛型类型参数

你知道如何处理这个问题吗

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

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

在Rust等具有良好类型推断的语言中,通常不需要使用过多的类型注释来污染代码。因此,如果您需要更改一个具体的类型,那么需要更新的地方就更少了,而且问题也不会像Java这样的语言那样严重

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

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

那么,回到你的第一个问题:

如何解决这个问题?

如果您确实想这样做,可以为mapper函数引入另一个trait对象。trait对象不能有一个具有自己泛型参数的方法的原因是,编译器在编译时无法知道将要到达的对象的大小。因此,只需将函数参数也转换为trait对象,就可以解决这个问题:

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

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

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

在Rust等具有良好类型推断的语言中,通常不需要使用过多的类型注释来污染代码。因此,如果您需要更改一个具体的类型,那么需要更新的地方就更少了,而且问题也不会像Java这样的语言那样严重

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

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

那么,回到你的第一个问题:

如何解决这个问题?

如果您确实想这样做,可以为mapper函数引入另一个trait对象。trait对象不能有一个具有自己泛型参数的方法的原因是,编译器在编译时无法知道将要到达的对象的大小。因此,只需将函数参数也转换为trait对象,就可以解决这个问题:

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

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

完全有可能创建您的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);
    }
}
pub特征图{
fn看跌期权(&mut self,k:k,v:v)->期权;
fn upsert_已删除(&self,key:K,value:V,updater:&fn(&mut V));
fn upsert(&self,键:K,值:V,更新程序:&U)
惠尔
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);
    }
}