【问题标题】:How do I use a &HashSet<&T> as an IntoIterator<Item = &T>?如何将 &HashSet<&T> 用作 IntoIterator<Item = &T>?
【发布时间】:2019-12-23 19:58:05
【问题描述】:

我有一个函数,它接受&amp;T(由IntoIterator 表示)的集合,并要求每个元素都是唯一的。

fn foo<'a, 'b, T: std::fmt::Debug, I>(elements: &'b I)
where
    &'b I: IntoIterator<Item = &'a T>,
    T: 'a,
    'b: 'a,

我还想编写一个包装函数,即使元素不是唯一的,它也可以工作,方法是先使用HashSet 删除重复的元素。

我尝试了以下实现:

use std::collections::HashSet;

fn wrap<'a, 'b, T: std::fmt::Debug + Eq + std::hash::Hash, J>(elements: &'b J)
where
    &'b J: IntoIterator<Item = &'a T>,
    T: 'a,
    'b: 'a,
{
    let hashset: HashSet<&T> = elements.into_iter().into_iter().collect();
    foo(&hashset);
}

playground

但是,编译器似乎对我假设 HashSet&lt;&amp;T&gt; 实现 IntoIterator&lt;Item = &amp;'a T&gt; 并不满意:

error[E0308]: mismatched types
  --> src/lib.rs:10:9
   |
10 |     foo(&hashset);
   |         ^^^^^^^^ expected type parameter, found struct `std::collections::HashSet`
   |
   = note: expected type `&J`
              found type `&std::collections::HashSet<&T>`
   = help: type parameters must be constrained to match other types
   = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters

我知道我可以通过克隆所有输入元素来使用HashSet&lt;T&gt;,但我想避免不必要的复制和内存使用。

【问题讨论】:

标签: rust lifetime


【解决方案1】:

如果您有一个&amp;HashSet&lt;&amp;T&gt; 并且需要一个可以多次处理的&amp;T(不是&amp;&amp;T)迭代器,那么您可以使用Iterator::copied 将迭代器的&amp;&amp;T 转换为&amp;T :

use std::{collections::HashSet, fmt::Debug, hash::Hash, marker::PhantomData};

struct Collection<T> {
    item: PhantomData<T>,
}

impl<T> Collection<T>
where
    T: Debug,
{
    fn foo<'a, I>(elements: I) -> Self
    where
        I: IntoIterator<Item = &'a T> + Clone,
        T: 'a,
    {
        for element in elements.clone() {
            println!("{:?}", element);
        }
        for element in elements {
            println!("{:?}", element);
        }
        Self { item: PhantomData }
    }
}

impl<T> Collection<T>
where
    T: Debug + Eq + Hash,
{
    fn wrap<'a, I>(elements: I) -> Self
    where
        I: IntoIterator<Item = &'a T>,
        T: 'a,
    {
        let set: HashSet<_> = elements.into_iter().collect();
        Self::foo(set.iter().copied())
    }
}

#[derive(Debug, Hash, PartialEq, Eq)]
struct Foo(i32);

fn main() {
    let v = vec![Foo(1), Foo(2), Foo(4)];
    Collection::<Foo>::wrap(&v);
}

另见:


请注意,此答案的其余部分假设名为Collection&lt;T&gt; 的结构是T 类型值的集合。 OP 已澄清这不是真的。

这不是您的问题,如您后面的示例所示。这可以归结为:

struct Collection<T>(T);

impl<T> Collection<T> {
    fn new(value: &T) -> Self {
        Collection(value)
    }
}

您正在引用一个类型 (&amp;T) 并尝试将其存储在需要 T 的位置;这些是不同的类型,会产生错误。您出于某种原因使用 PhantomData 并通过迭代器接受引用,但问题是一样的。

事实上,PhantomData 使问题更难被发现,因为你可以弥补不起作用的值。例如,我们这里从来没有任何类型的字符串,但我们“成功地”创建了结构:

use std::marker::PhantomData;

struct Collection<T>(PhantomData<T>);

impl Collection<String> {
    fn new<T>(value: &T) -> Self {
        Collection(PhantomData)
    }
}

最终,您的 wrap 函数也没有意义:

impl<T: Eq + Hash> Collection<T> {
    fn wrap<I>(elements: I) -> Self
    where
        I: IntoIterator<Item = T>,

这相当于

impl<T: Eq + Hash> Collection<T> {
    fn wrap<I>(elements: I) -> Collection<T>
    where
        I: IntoIterator<Item = T>,

也就是说,给定元素 T 的迭代器,您将返回这些元素的集合。但是,您将它们放在HashMap 中并迭代它的reference,从而产生&amp;T。因此这个函数签名不可能是正确的。

您似乎很可能希望接受拥有值的迭代器:

use std::{collections::HashSet, fmt::Debug, hash::Hash};

struct Collection<T> {
    item: T,
}

impl<T> Collection<T> {
    fn foo<I>(elements: I) -> Self
    where
        I: IntoIterator<Item = T>,
        for<'a> &'a I: IntoIterator<Item = &'a T>,
        T: Debug,
    {
        for element in &elements {
            println!("{:?}", element);
        }
        for element in &elements {
            println!("{:?}", element);
        }

        Self {
            item: elements.into_iter().next().unwrap(),
        }
    }
}

impl<T> Collection<T>
where
    T: Eq + Hash,
{
    fn wrap<I>(elements: I) -> Self
    where
        I: IntoIterator<Item = T>,
        T: Debug,
    {
        let s: HashSet<_> = elements.into_iter().collect();
        Self::foo(s)
    }
}

#[derive(Debug, Hash, PartialEq, Eq)]
struct Foo(i32);

fn main() {
    let v = vec![Foo(1), Foo(2), Foo(4)];
    let c = Collection::wrap(v);
    println!("{:?}", c.item)
}

这里我们直接在泛型迭代器类型上放置一个特征绑定,并在对迭代器的引用上放置第二个更高级别的特征绑定。这允许我们使用对迭代器的引用作为迭代器本身。

另见:

【讨论】:

  • 您认为我的 T 集合实际上必须包含 T 类型的自有值的假设是错误的。该数据结构是一个散列 bashed 概率数据结构,因此它实际上并不存储类型 T 的拥有值,但是对于类型安全来说,跟踪类型 T 是很重要的。我刚刚检查了bloomfilter 库是如何实现的它和they use exactly the same pattern。对于这种类型的数据结构,您还应该如何确保类型安全?
  • 您的示例还巧妙地将问题从使用&amp;HashSet&lt;&amp;T&gt; 作为IntoIterator&lt;Item=&amp;T&gt; 更改为另一个更简单的问题,即使用&amp;HashSet&lt;&amp;T&gt; 作为IntoIterator&lt;Item=&amp;&amp;T&gt; 或使用&amp;HashSet&lt;T&gt; 作为IntoIterator&lt;Item=&amp;T&gt;。因为函数在 T 上是独立通用的,所以 foo 中的 T 与代码中 wrap 中的 T 不同的事实被隐藏了。实际上,您的示例使用&amp;HashSet&lt;&amp;T&gt; 作为IntoIterator&lt;Item=&amp;&amp;T&gt;,正如您所指出的,标准库已经实现了。
  • 非常感谢您的反馈,如果没有您的帮助,我将完全不可能解决这个问题,但随着我了解更多,我仍然认为在我的用例中,包装类是必需的(我很乐意被证明是错误的)。我没有在这里倾倒我的整个代码库,而是尝试发布一个小型的自包含复制品,但我不确切知道什么是相关的,什么是多余的。这就是为什么我最初的问题没有明确所有要求(我不知道它们是否相关!)。感谢您的耐心和帮助。
  • @JeremySalwen 已更新。你是对的,我不匹配&amp;T 和&amp;&amp;T,但幸运的是,这很容易解决。您也是正确的,我根据提供的名称对您的原始代码进行了假设(我认为bloomfilter 等是不是 collections)。
  • 谢谢,您的最新回答解决了我的问题的核心:.copied() 将&amp;&amp;T 转换为&amp;T,我可以将迭代器直接传递给implementIntoIterator。我只有一个细节,即实现将接受任何实现Clone 的类型I。例如,如果有人传入Vec&lt;T&gt;,它会很高兴地克隆Vec,而不是抱怨用户应该使用&amp;Vec&lt;T&gt;。我想我可以通过在任何地方使用 Iterator 特征而不是 IntoIterator 来解决这个问题。
【解决方案2】:

Shepmaster 指出我的代码存在许多正交问题,但为了解决将HashSet&lt;&amp;T&gt; 用作IntoIterator&lt;Item=&amp;T&gt; 的问题,我发现解决它的一种方法是使用包装器结构:

struct Helper<T, D: Deref<Target = T>>(HashSet<D>);

struct HelperIter<'a, T, D: Deref<Target = T>>(std::collections::hash_set::Iter<'a, D>);

impl<'a, T, D: Deref<Target = T>> Iterator for HelperIter<'a, T, D>
where
    T: 'a,
{
    type Item = &'a T;
    fn next(&mut self) -> Option<Self::Item> {
        self.0.next().map(|x| x.deref())
    }
}

impl<'a, T, D: Deref<Target = T>> IntoIterator for &'a Helper<T, D> {
    type Item = &'a T;
    type IntoIter = HelperIter<'a, T, D>;
    fn into_iter(self) -> Self::IntoIter {
        HelperIter((&self.0).into_iter())
    }
}

具体用法如下:

struct Collection<T> {
    item: PhantomData<T>,
}

impl<T: Debug> Collection<T> {
    fn foo<I>(elements: I) -> Self
    where
        I: IntoIterator + Copy,
        I::Item: Deref<Target = T>,
    {
        for element in elements {
            println!("{:?}", *element);
        }
        for element in elements {
            println!("{:?}", *element);
        }
        return Self { item: PhantomData };
    }
}

impl<T: Debug + Eq + Hash> Collection<T> {
    fn wrap<I>(elements: I) -> Self
    where
        I: IntoIterator + Copy,
        I::Item: Deref<Target = T> + Eq + Hash,
    {
        let helper = Helper(elements.into_iter().collect());
        Self::foo(&helper);
        return Self { item: PhantomData };
    }
}

fn main() {
    let v = vec![Foo(1), Foo(2), Foo(4)];
    Collection::<Foo>::wrap(&v);
}

我猜其中一些可能比它需要的更复杂,但我不确定如何。

full playground

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-06-05
    • 2011-12-13
    • 2011-08-20
    • 2015-09-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-08
    相关资源
    最近更新 更多