在Rust中使用接受闭包的方法创建对象安全的特性 [英] Create an Object-safe Trait in Rust with a method that accepts a closure

查看:81
本文介绍了在Rust中使用接受闭包的方法创建对象安全的特性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想使用以下定义为Map创建一个特征:

I want to create a trait for Map with the following definition:

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 ),那么我将无法获得这样的表达式:

Now, the problem is that if I implement this trait, for example as MyHashMap, then I cannot have an expression like this:

let map: Box<Map<i32, i32>> = Box::new(MyHashMap::<i32, i32>::new());

错误将是:

特征map::Map不能制成对象

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

How can one solve this issue? Because it's not a good idea to start using a Map implementation directly, as it's not a good software engineering practice.

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

The main issue is that get and upsert methods in the trait accept generic type parameters. My first attempt was to get rid of these generic type parameters.

对于 get 方法,即使它与rust集合中 get 的通用签名有所不同,也可能使它的使用场景受到限制.结果如下:

For get method, it's possible, even though it deviates from the common signature of get in rust collections and makes its usage scenarios more limited. Here is the result:

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 中的泛型类型参数的方式一无所知.

However, I do not have any idea about the way to remove the generic type parameter in upsert.

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

Any idea on how to deal with this issue?

推荐答案

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

How can one solve this issue? Because it's not a good idea to start using a Map implementation directly, as it's not a good software engineering practice.

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

This is good practice in Java, but not necessarily in other languages. For example, in dynamically typed languages, provided all Map implementations use the same naming convention for the methods, they can be substituted without a lot of code changes.

在Rust等具有良好类型推断功能的语言中,通常不需要使用过多的类型标注来污染代码.结果,如果您需要更改具体类型,则需要更新的地方就更少了,这比在Java之类的语言中遇到的问题要少.

In languages like Rust, which have good type inference, you don't typically need to pollute the code with excessive type annotations. As a result, if you need to change a concrete type, there are fewer places that need to be updated, and it is less of a problem than you'd find in languages like Java.

良好" Java的隐含目标是,您可能希望在运行时交换抽象类型的任何实现. Java使此操作易于实现,因此这样做是合理的,尽管实际上很少需要这样做.更有可能,您将使用一些期望抽象类型的代码,并为它提供一个具体实例,该实例在编译时已知.

"Good" Java has the implicit goal that you might want to swap any implementation of your abstract type at run-time. Java makes this easy to do, so it's reasonable to do it, even though, in practice, this is needed very rarely. More likely, you will use some code that expects an abstract type, and you provide it with a concrete instance which is known at compile-time.

这正是Rust与参数一起工作的方式.指定M: Map参数时,可以使用 any M来实现Map.但是编译器会在编译时弄清楚您实际使用的是哪种具体实现(这称为同构化").如果您需要更改具体的实现,则只需更改一行代码即可.这对于性能也有巨大的好处.

This is exactly how Rust works with parameters. When you specify a M: Map parameter, you can work to any M which also implements Map. But the compiler will figure out at compile-time which concrete implementation you are actually using (this is called monomorphization). If you need to change the concrete implementation, you can do so by changing just one line of code. This also has huge benefits for performance.

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

So, coming back to your first question:

如何解决这个问题?

How can one solve this issue?

如果您确实想执行此操作,则可以为mapper函数引入另一个特征对象.特质对象不能具有带有其自己的通用参数的方法的原因是,因为编译器无法在编译时知道将要存放的内容的大小.因此,也只需将您的函数参数设置为特征对象,这样就可以解决此问题:

If you really want to do this, you can introduce another trait object for the mapper function. The reason why a trait object can't have a method with its own generic arguments is because the compiler can't know at compile-time the size of whatever will go there. So just make your function argument into a trait object too, so that this problem goes away:

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

但是,如上所述,我的真实答案是使事情保持简单.如果您确实需要此Map抽象,则它应该与在编译时已知实例化的类型参数一起很好地工作.在无法在编译时知道具体类型的情况下(例如,在运行时可以更改实现的情况下),请使用trait对象.

But my real answer is, as I've described above, to keep things simple. If you really need this Map abstraction, it should work perfectly well with type parameters whose instantiation is known at compile-time. Use trait objects for when a concrete type cannot be known at compile-time, for example where the implementation can change at run-time.

这篇关于在Rust中使用接受闭包的方法创建对象安全的特性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆