如何编写一个带有字符串迭代器的 trait 方法,避免单态化(静态调度)? [英] How to write a trait method taking an iterator of strings, avoiding monomorphization (static dispatch)?
问题描述
我想定义一个特征,它有一个操作字符串序列的方法.同时,我想避免在 trait 中使用泛型方法,也就是静态分派,以便我可以使用这个 trait 作为 trait 对象.到目前为止,我得到的最佳解决方案是按照以下方式进行操作:
I want to define a trait that has a method operating on sequences of strings. At the same time, I want to avoid having generic methods, a.k.a. static dispatch, in the trait, so that I can use this trait as a trait object. The best solution I was given till now was to do it like below:
pub trait Store {
fn query_valid_paths(&mut self, paths: &mut dyn Iterator<Item = &str>) -> Vec<String>;
}
不幸的是,它并不完美:
Unfortunately, it's not perfect:
它仅适用于
&str
的迭代器;对于String
的迭代器,例如Vec
,我必须用以下神奇的map
咒语来调用它,这真的很难看,而且我永远不会在没有帮助的情况下将其发明为新手:
It works out of the box only for iterators of
&str
; for iterators ofString
, e.g.Vec<String>
, I have to call it with the following magicmap
incantation, which is really ugly, and I'd never invent it as a newbie without help:
// `vec` is a std::vec::Vec<String>
store.query_valid_paths(&mut vec.iter().map(|s| &**s));
它需要一个 Iterator
,但如果我可以使用 IntoIterator
,我会很高兴.换句话说,我希望能够像这样称呼它:
It takes an Iterator
, but I'd love if I could take an IntoIterator
. In other words, I'd like to be able to call it just like this:
store.query_valid_paths(&vec);
有可能吗?
基于一个更简单的关于采用字符串迭代器的函数的问题,我认为这样的事情可以工作:>
Based on a simpler question about functions taking string iterators, I'd imagine something like this could work:
pub trait Store {
fn query_valid_paths<S>(&mut self, paths: impl IntoIterator<Item = S>) -> Vec<String>
where
S: AsRef<str>;
}
但这似乎使它成为通用方法",触发静态调度...
but this seems to make it a "generic method", triggering static dispatch...
有人向我提出了另一个关于 Rust discord 的想法,具体来说:
I was suggested another idea on Rust discord, specifically:
pub trait Store {
fn query_valid_paths_inner(&mut self, paths: &mut dyn Iterator<Item = &str>) -> Vec<String>;
}
impl dyn Store {
pub fn query_valid_paths<'a>(&mut self, paths: impl IntoIterator<Item = &'a str>) -> Vec<String> {
let mut it = paths.into_iter();
self.query_valid_paths_inner(&mut it)
}
}
——但是当我尝试向其中添加 AsRef
String
和 &str
迭代器...
— but when I try to add AsRef<str>
to it, I'm getting lifetime errors, and cannot seem to make it work for both String
and &str
iterators...
推荐答案
我推荐你阅读 这个问题 ,其中有很多很好的信息,说明如果要将泛型用作特征方法,为什么不能将它们用作对象.
I recommend you read this question, which has lots of good information about why you can't use generics with trait methods if you want to use them as objects.
简短的回答是你不能做你想做的事:有一个函数接受任何类型的迭代器(这是一个关联的泛型函数)并且仍然具有对象安全的特征.
The short answer is that you can't do what you're trying to do: have a function that takes in an iterator of any type (which is an associated generic function) and still have the trait be object safe.
不过,您可以使用一些技巧,它们可以让您使用 trait 对象操作字符串迭代器.我将介绍每种方法.
There are a few tricks you can use, though, that will let you manipulate string iterators with a trait object. I'll go over each method.
Rust 只有两种字符串:String
和 &str
.正如您在回答中所述,您希望同时使用两者.在这种情况下,您需要做的就是创建两种不同的方法:
Rust only has two kinds of strings: String
and &str
. As you've stated in your answer, you want to work with both. In this case, all you need to do is make two different methods:
pub trait Store {
fn query_valid_paths_str(&mut self, paths: &mut dyn Iterator<Item = &str>) -> Vec<String>;
fn query_valid_paths_string(&mut self, paths: &mut dyn Iterator<Item = String>) -> Vec<String>;
}
现在,如果您要处理的类型太多,这在某些时候会变得违反直觉.但如果只有两个,这是最直接的选择.
Now, this gets counter-intuitive at a certain point, if you have too many types you're dealing with. But if there's only two, this is the most straightforward option.
如果您想改用 IntoIterator
,函数签名将如下所示:
If you're wanting to use IntoIterator
instead, the function signatures will look like this:
pub trait Store {
fn query_valid_paths_str(&mut self, paths: &mut dyn IntoIterator<IntoIter = IntoIter<&str>, Item = &str>) -> Vec<String>;
fn query_valid_paths_string(&mut self, paths: &mut dyn IntoIterator<IntoIter = IntoIter<String>, Item = String>) -> Vec<String>;
}
2.使用 Box
和动态调度
这种方法涉及更多,可能不值得付出努力,但我将把它放在这里作为概念证明.
2. Use Box
and dynamic dispatch
This approach is much more involved, and probably not worth the effort, but I'll put it here as a proof of concept.
pub trait Store {
fn query_valid_paths(&mut self, paths: &mut dyn Iterator<Item = &Box<dyn AsRef<str>>) -> Vec<String>;
}
这里,paths
是一个在拥有 AsRef
trait 对象的盒子上的迭代器.
Here, paths
is an iterator over a box which owns an AsRef<str>
trait object.
这是(据我所知)创建真正多态解决方案的唯一方法.但代价是什么?为此,您不仅需要将传入的列表显式声明为 Vec
,它还增加了大量开销,并从框指针.只是为了说明这有多麻烦:
This is (as far as I know) the only way to create a truly polymorphic solution. But at what cost? For this to work, you not only need to explicitly declare the list you passed in as a Vec<Box<AsRef<str>>>
, it adds a lot of overhead with dynamic dispatch from the box pointers. Just to show how cumbersome this can be:
let mut str_vec: Vec<Box<AsRef<str>>> = vec!(Box::new("string one"), Box::new("string two".to_string()));
some_store_object.query_valid_paths(&mut str_vec.iter());
除非您绝对需要此功能,否则我不推荐此方法.改用第一种方法.
I do not recommend this method unless you absolutely need this functionality. Use the first method instead.
如果你确实使用了这个方法,但想和 IntoIterator
一起使用,它看起来像这样:
If you do use this method, but want to use it with IntoIterator
, it would look like this:
pub trait Store {
fn query_valid_paths(&mut self, paths: &mut dyn IntoIterator<IntoIter = IntoIter<Box<dyn AsRef<str>>>, Item = Box<dyn AsRef<str>>>) -> Vec<String>;
}
这篇关于如何编写一个带有字符串迭代器的 trait 方法,避免单态化(静态调度)?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!