Modifying the inserted HashSet element without affecting its Hash and Eq

I have a code like below:

#[derive(Debug)]
struct Entry {
    name:String,
    year:u32
}
fn main() {
let mut set = HashSet::new();
   set.insert(Entry {name:"hongkong".to_string(), year: 1957});
   set.insert(Entry {name:"dubai".to_string(), year: 1987});
    assert_eq!{false,set.insert(Entry {name:"dubai".to_string(), year: 2002})}
    for entry in &mut set {
        entry.year = 2025;
        println!{"{entry:?}"}
    }   
}

impl PartialEq for Entry {
    fn eq(&self, other: &Self) -> bool {
        self.name == other.name
    }
}

impl Eq for Entry {}

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

It gives me a compilation error like:

Compiling main ...
error[E0277]: &mut HashSet<Entry> is not an iterator
--> main.rs:54:18
|
54 | f
or entry in &mut set {
| ^^^^^^^^ &mut HashSet<Entry> is not an iterator
|

help: the trait Iterator is not implemented for HashSet<Entry>, which is required by &mut HashSet<Entry>: IntoIterator
= note: required for &mut HashSet<Entry> to implement
Iterator
= note: required for &mut HashSet<Entry> to implement IntoIterator

I assume that Rust wants to avoid that HashSet entry is changed and can issue a clash of duplicate entries. Is there a way of a simple work around without a creation of own HashSet implementation? Or maybe I just overlooked a simple mistake?

The easiest way would be to use interior mutability (Cell, RefCell, etc). You probably already know this, but it’s a logic error to use interior mutability to change the hash code (but I believe it is not unsafe).

In other words,

    year: Cell<u32>

and iterate over &set.

Since only name is used for Eq and Hash, there is no problem.

Thanks, it worked. I assume that if I have several elements I want to modify in the struct, then better to group them in a separate struct and add it in a cell .

The Cell has no space overhead. So you can group by granularity of updates.

1 Like

not only no space overhead, no runtime overhead at all in most cases[1]


  1. although you may end up doing extra reads in some niche scenarios due to lack of noalias semantics ↩︎