Problematic errormessage: " `IndexMut` is required to modify indexed content"

Friends

I am trying to extract the unique members of a collection of usize.

I have boiled my code down to:

	let mut unique_things: HashMap<usize, bool>  = HashMap::new();
	unique_things[&5] = false;

The error message

error[E0594]: cannot assign to data in an index of `HashMap<usize, bool>`
   --> src/main.rs:147:2
    |
147 |     unique_things[&5] = false;
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^ cannot assign
    |
    = help: trait `IndexMut` is required to modify indexed content, but it is not implemented for `HashMap<usize, bool>`

leaves me cold. The part that confuses me is:

147 |     unique_things[&5] = false;
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^ cannot assign

unique_things is mutable so why is the trait IndexMut not satisfied?

HashMap doesn't have any implementations of IndexMut, you can use insert or the entry API instead.

You can see a bit more about why the implementation was removed on the PR that removed it.

2 Likes

Thank you for that. That PR is from 2015. I was sure I had used that syntax much more recently than that.

To be perfectly clear:

let mut map:HashMap<usize, bool>  = HashMap::new();
map[&7] = false;

is invalid and must be....

let mut map:HashMap<usize, bool>  = HashMap::new();
map.insert(7, false);

????

(I think I was confusing Rust and Swift, sigh)

That's correct. It is a bit confusing that HashMap implements Index and not IndexMut. If we look at the definition of the trait though, it's fairly straightforward to see why implementing it for HashMap is fraught.

pub trait IndexMut<Idx>: Index<Idx> where
    Idx: ?Sized, {
    fn index_mut(&mut self, index: Idx) -> &mut Self::Output;
}

IndexMut returns an exclusive reference to the value based on the index parameter. That means the trait can really only return references to values that are already in the collection. Otherwise we'd need some sort of default value for the type to fill in that memory with before we created a reference to it.

Since most languages that support indexing syntax on map-like collections usually DO allow you to add items to the collection via indexing, it can be a little confusing for new users. That was at least part of the rationale for removing the implementation on the map types in std. There may eventually be another Index trait that allows assignment in a way that would support this but for now the dedicated methods are the way to go.

4 Likes

Good. I see. Been in Swift too long.

But what is this syntax?

It means Idx can be a dynamically sized type, i.e. the compiler may not know the size of the type directly. Sized is currently the only trait you can use the ? syntax on to indicate it may or may not be implemented.

I'm not actually sure why that bound is there, it might be related to the currently unstable unsized_locals feature.

1 Like

Filed Improve diagnostic for missing `IndexMut: HashMap` · Issue #100873 · rust-lang/rust · GitHub

1 Like

it's kinda related to that , but that's not the main reason.
it's because when you declare a trait or generic in any place the compiler implicitly adds a bound that it is Sized, so that (speculation) it can compile those without demanding Sized bounds everywhere until unsized_locals is stabilized. (/end speculation)

but in this case, since it takes a &mut self, it does not need the Sized bound, and so, to allow !Sized types too, hence the ?Sized stuff.

i'm not sure why the compiler is not smart enough to do these kinds of stuff automatically tho.

Right, but in Index T is a by-value parameter, so you can't actually implement it with a DST like Index<str> or Index<dyn Debug> without running face first into a compiler error (which happens to mention unsized_locals).

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.