如何从 HashMap 或 HashSet 中 return get_mut 的结果?
How do I return the result of get_mut from a HashMap or a HashSet?
我正在尝试将 HashMap
包装成 return 来自 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
不提供可变的 getter(这是有道理的,因为您不想改变用于哈希的内容)。但是在我的例子中,只有部分对象应该是不可变的。将 const
类型转换为非 const
类型是 UB,所以这是不可能的。
我能达到我想要的吗?我是否必须使用一些包装器,例如 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()
}
}
如果我正确理解了您要实现的目标,那么我必须告诉您,它比您原先想象的要复杂一些。
首先,你必须意识到,如果你喜欢使用 HashMap
那么密钥的类型需要是 hashable 和 相当。因此,Id<T>
中的泛型类型参数 T
必须绑定到这些特征,以使 Id
可散列和可比较。
您需要了解的第二件事是处理索引运算符有两种不同的特征:Index
for immutable data access, and 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
.
修改它
我正在尝试将 HashMap
包装成 return 来自 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
不提供可变的 getter(这是有道理的,因为您不想改变用于哈希的内容)。但是在我的例子中,只有部分对象应该是不可变的。将 const
类型转换为非 const
类型是 UB,所以这是不可能的。
我能达到我想要的吗?我是否必须使用一些包装器,例如 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()
}
}
如果我正确理解了您要实现的目标,那么我必须告诉您,它比您原先想象的要复杂一些。
首先,你必须意识到,如果你喜欢使用 HashMap
那么密钥的类型需要是 hashable 和 相当。因此,Id<T>
中的泛型类型参数 T
必须绑定到这些特征,以使 Id
可散列和可比较。
您需要了解的第二件事是处理索引运算符有两种不同的特征:Index
for immutable data access, and 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
.