首页 > 解决方案 > 如何从 HashMap 或 HashSet 返回 get_mut 的结果?

问题描述

我正在尝试包装 a HashMap,如下定义,以从 a 返回可变引用HashMap

use std::{collections::HashMap, marker::PhantomData};

struct Id<T>(usize, PhantomData<T>);
pub struct IdCollection<T>(HashMap<Id<T>, T>);

impl<'a, T> std::ops::Index<Id<T>> for &'a mut IdCollection<T> {
    type Output = &'a mut T;
    fn index(&mut self, id: &'a Id<T>) -> Self::Output {
        self.0.get_mut(id).unwrap()
    }
}

以及由此产生的错误:

note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 54:5...
  --> src/id_container.rs:54:5
   |
54 | /     fn index(&mut self, id: &'a Id<T>) -> Self::Output {
55 | |         self.0.get_mut(id).unwrap()
56 | |     }
   | |_____^
note: ...so that reference does not outlive borrowed content
  --> src/id_container.rs:55:9
   |
55 |         self.0.get_mut(id).unwrap()
   |         ^^^^^^
note: but, the lifetime must be valid for the lifetime 'a as defined on the impl at 52:6...
  --> src/id_container.rs:52:6
   |
52 | impl<'a, T> std::ops::Index<Id<T>> for &'a mut IdCollection<T> {
   |      ^^
   = note: ...so that the types are compatible:
           expected std::ops::Index<id_container::Id<T>>
              found std::ops::Index<id_container::Id<T>>

为什么编译器不能延长生命周期get_mut?然后IdCollection将可变地借用。

请注意,我尝试使用 astd::collections::HashSet<IdWrapper<T>>而不是 a HashMap

struct IdWrapper<T> {
  id: Id<T>,
  t: T,
}

实施适当的借用等,以便我可以使用Id<T>作为密钥。但是,HashSet不提供可变的 getter(这是有道理的,因为您不想改变用于哈希的内容)。但是在我的情况下,只有部分对象应该是不可变的。将const类型转换为非constUB 所以这是不可能的。

我能达到我想要的吗?我是否必须使用一些包装器,例如 a Box?虽然我宁愿避免任何间接......

编辑

好吧,我是个白痴。首先,我错过了IndexMut而不是,并且在签名中指定时Index忘记了。&Self::Output

下面是我的完整代码:

pub struct Id<T>(usize, PhantomData<T>);
impl<T> std::fmt::Display for Id<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl<T> Hash for Id<T> {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.0.hash(state);
    }
}

impl<T> PartialEq for Id<T> {
    fn eq(&self, o: &Self) -> bool {
        self.0 == o.0
    }
}
impl<T> Eq for Id<T> {}

pub struct IdCollection<T>(HashMap<Id<T>, T>);
impl<'a, T> IntoIterator for &'a IdCollection<T> {
    type Item = (&'a Id<T>, &'a T);
    type IntoIter = std::collections::hash_map::Iter<'a, Id<T>, T>;

    fn into_iter(self) -> Self::IntoIter {
        self.0.iter()
    }
}

impl<'a, T> IntoIterator for &'a mut IdCollection<T> {
    type Item = (&'a Id<T>, &'a mut T);
    type IntoIter = std::collections::hash_map::IterMut<'a, Id<T>, T>;

    fn into_iter(self) -> Self::IntoIter {
        self.0.iter_mut()
    }
}

impl<T> std::ops::Index<Id<T>> for IdCollection<T> {
    type Output = T;
    fn index(&self, id: Id<T>) -> &Self::Output {
        self.0.get(&id).unwrap()
    }
}

impl<T> std::ops::IndexMut<Id<T>> for IdCollection<T> {
    fn index_mut(&mut self, id: Id<T>) -> &mut Self::Output {
        self.0.get_mut(&id).unwrap()
    }
}

impl<T> std::ops::Index<&Id<T>> for IdCollection<T> {
    type Output = T;
    fn index(&self, id: &Id<T>) -> &Self::Output {
        self.0.get(id).unwrap()
    }
}

impl<T> std::ops::IndexMut<&Id<T>> for IdCollection<T> {
    fn index_mut(&mut self, id: &Id<T>) -> &mut Self::Output {
        self.0.get_mut(id).unwrap()
    }
}

标签: rustborrow-checker

解决方案


如果我正确理解你试图达到的目标,那么我必须告诉你,它比你最初想象的要复杂一些。

首先,你必须意识到,如果你喜欢使用 aHashMap那么 key 的类型需要是hashable可比较的。因此,泛型类型参数TinId<T>必须绑定到这些特征,以使Id可散列和可比较。

您需要了解的第二件事是处理索引运算符有两种不同的特征:Index用于不可变数据访问,以及IndexMut用于可变数据访问。

use std::{
    marker::PhantomData,
    collections::HashMap,
    cmp::{
        Eq,
        PartialEq,
    },
    ops::{
        Index,
        IndexMut,
    },
    hash::Hash,
};

#[derive(PartialEq, Hash)]
struct Id<T>(usize, PhantomData<T>)
    where T: PartialEq + Hash;

impl<T> Eq for Id<T>
    where T: PartialEq + Hash
{}

struct IdCollection<T>(HashMap<Id<T>, T>)
    where T: PartialEq + Hash;

impl<T> Index<Id<T>> for IdCollection<T>
    where T: PartialEq + Hash
{
    type Output = T;

    fn index(&self, id: Id<T>) -> &Self::Output
    {
        self.0.get(&id).unwrap()
    }
}

impl<T> IndexMut<Id<T>> for IdCollection<T>
    where T: PartialEq + Hash
{
    fn index_mut(&mut self, id: Id<T>) -> &mut Self::Output
    {
        self.0.get_mut(&id).unwrap()
    }
}

fn main()
{
    let mut i = IdCollection(HashMap::new());
    i.0.insert(Id(12, PhantomData), 99i32);
    println!("{:?}", i[Id(12, PhantomData)]);
    i[Id(12, PhantomData)] = 54i32;
    println!("{:?}", i[Id(12, PhantomData)]);
}

这可能看起来有点令人惊讶,但IndexMut它并非旨在将元素插入集合中,而是实际修改现有元素。这就是为什么HashMap不实现IndexMut的主要原因——这也是为什么上面的示例使用该HashMap::insert方法来初始放置数据的原因。如您所见,稍后,当该值已经可用时,我们可以通过IdCollection::index_mut.


推荐阅读