Как мне вернуть результат get_mut из HashMap или HashSet? - PullRequest
0 голосов
/ 20 июня 2019

Я пытаюсь обернуть HashMap, как определено ниже, чтобы вернуть изменяемую ссылку из 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 будет затем заимствовано.

Обратите внимание, что я попытался использовать std::collections::HashSet<IdWrapper<T>> вместо HashMap:

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

Реализация правильного заимствования и т. Д., Чтобы я мог использовать Id<T> в качестве ключа. Однако HashSet не предлагает изменяемый метод получения (что имеет смысл, поскольку вы не хотите изменять то, что используется для вашего хэша). Однако в моем случае только часть объекта должна быть неизменной. Приведение типа const к типу, отличному от const, является UB, так что об этом не может быть и речи.

Могу ли я достичь того, чего хочу? Нужно ли использовать какую-нибудь обертку, например Box? Хотя я бы предпочел избежать любой косвенности ...

EDIT

Хорошо, я идиот. Сначала я пропустил 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()
    }
}

1 Ответ

1 голос
/ 20 июня 2019

Если я правильно понимаю, чего вы пытаетесь достичь, тогда я должен сказать вам, что это немного сложнее, чем вы изначально думали, что это будет.

Прежде всего, вы должны понимать, что если вы хотите использовать HashMap, тогда тип ключа должен быть hashable и сопоставимый . Поэтому параметр универсального типа T в Id<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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...