如何编写一个带有字符串迭代器的 trait 方法,避免单态化(静态调度)? [英] How to write a trait method taking an iterator of strings, avoiding monomorphization (static dispatch)?

查看:31
本文介绍了如何编写一个带有字符串迭代器的 trait 方法,避免单态化(静态调度)?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想定义一个特征,它有一个操作字符串序列的方法.同时,我想避免在 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:

  1. 它仅适用于 &str 的迭代器;对于 String 的迭代器,例如Vec,我必须用以下神奇的map咒语来调用它,这真的很难看,而且我永远不会在没有帮助的情况下将其发明为新手:

  1. It works out of the box only for iterators of &str; for iterators of String, e.g. Vec<String>, I have to call it with the following magic map 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屋!

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