如何编写一个接受字符串迭代器并避免单态化(静态分发)的特质方法?

9
我想定义一个具有操作字符串序列的方法的特质。同时,我想避免在特质中使用通用方法(即静态分发),以便我可以将此特质用作特质对象。到目前为止,我得到的最好解决方案是像下面这样做:
pub trait Store {
    fn query_valid_paths(&mut self, paths: &mut dyn Iterator<Item = &str>) -> Vec<String>;
}

不幸的是,它并不完美:

  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));
    
  2. 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);
    
这是一个英文文本,翻译成中文如下:

可能吗?

失败尝试1

基于一个更简单的关于函数接受字符串迭代器的问题,我想像这样的方式可能会起作用:

pub trait Store {
    fn query_valid_paths<S>(&mut self, paths: impl IntoIterator<Item = S>) -> Vec<String>
    where
        S: AsRef<str>;
}

但是这似乎使它成为一个“通用方法”,触发静态分派...
第二次尝试失败
我在 Rust Discord 上得到了另一个建议,具体来说:
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<str>添加到它时,我遇到了生命周期错误,并且似乎无法使它适用于String&str迭代器...

为什么你不喜欢静态分派? - Stargateur
3
静态分发意味着你不能将其用作特质对象。 - ThatOneDeveloper
3个回答

4

我建议您阅读这个问题,其中包含了关于为什么如果要将它们用作对象,则不能在 trait 方法中使用泛型的许多好信息。

简短的答案是:您无法做到您尝试做的事情:有一个函数接受任何类型的迭代器(这是一个关联泛型函数),并仍然使 trait 对象安全。

但是,您可以使用一些技巧,这样就可以使用 trait 对象操作字符串迭代器。我将介绍每种方法。

1. 在 trait 中使用多个方法

Rust 只有两种字符串类型:String&str。如您在答案中所述,您想使用这两种类型进行操作。在这种情况下,您只需要创建两个不同的方法:

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>;
}

当你处理的类型过多时,这些内容可能会变得不太直观。但是如果只有两种类型,这是最直接的选项。

如果你想使用 IntoIterator,函数签名将如下所示:

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和动态分发

这种方法更加复杂,可能不值得花费精力,但我会将其放在这里作为概念验证。

pub trait Store {
    fn query_valid_paths(&mut self, paths: &mut dyn Iterator<Item = &Box<dyn AsRef<str>>) -> Vec<String>;
}

在这里,paths是一个迭代器,它拥有一个AsRef<str>特质对象的盒子。
据我所知,这是创建真正多态解决方案的唯一方法。但代价是什么?为了使其工作,你不仅需要明确声明传递的列表为Vec<Box<AsRef<str>>>,还需要通过盒子指针进行动态调度,这增加了很多开销。只是为了展示这可能会变得繁琐:
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());

我不建议使用此方法,除非您绝对需要这个功能。请使用第一种方法替代。
如果您确实要使用这种方法,并想将其与IntoIterator一起使用,则应该像这样写:
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>;
}

谢谢!第二种方法似乎仍然没有真正改善我的第一个标准,即“丑陋”,无论如何——也就是说,仍然似乎不能简单地将Vec<String>[&str]作为paths传递而不进行一些华丽而复杂的转换...(尽管可以说可能比.map(|s| &**s)稍微容易发明?) - akavel
你说得对,不幸的是这是传递动态 AsRef 特质对象的唯一方法。它必须由一个盒子指针后面的特质对象来表示。否则,你就必须使用泛型。 - ThatOneDeveloper
谢谢!(1)对我有用。虽然我还不确定为什么无法将“Iterator”替换为“IntoIter”。 - Marco Lazzeri

2
我认为没有静态分发的好解决方案。但是关于具有泛型参数方法的特质对象错误的文档实际上提供了这种情况的解决方案:
首先,你需要使用where Self: Sized标记你的方法——这使其在特质对象中不可用。也许你不需要在特质对象上下文中使用该方法——那么你已经完成了。
如果你需要在特质对象上下文中使用该方法,你可以通过一个包含你的特质对象的大小类型(例如Box)使其再次可用:
struct MyStruct(i32);

pub trait Store {
    fn len_of_first_str(&self, paths: impl IntoIterator<Item = impl AsRef<str>>) -> usize
    where Self: Sized{
        paths.into_iter().next().unwrap().as_ref().len()
    }
}

impl Store for Box<dyn Store>{}

fn myfun(arg: Box<dyn Store>) -> usize {
    arg.len_of_first_str(vec!["string"])
}

我从来不知道你可以在包装的 trait 对象上使用仅适用于大小特质的方法。这非常有趣! - ThatOneDeveloper

0
除了 Box 方法外,还可以在特质定义中定义通用类型。该方法受实现的类型的限制,并使用静态分派,但您的特质不会违反对象安全规则。
trait Store<'a, I>
where
    I: IntoIterator<Item = &'a str>,
{
    fn query_valid_paths(&mut self, iter: I) -> Vec<String>;
}

impl<'a, I> Store<'a, I> for ()
where
    I: IntoIterator<Item = &'a str>,
{
    fn query_valid_paths(&mut self, iter: I) -> Vec<String> {
        iter.into_iter().map(|x| x.to_string()).collect()
    }
}

// Store is object safe
struct _Bar<'a, I> {
    vec: Vec<Box<dyn Store<'a, I>>>,
}

fn main() {
    let vec_of_strings = vec!["one", "two", "three"];
    println!("{:?}", ().query_valid_paths(vec_of_strings));
}

为避免所有权问题,您可以使用vec_of_strings.iter().cloned()代替。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接