The second example is certainly possible if we made more extensive annotations. For example, instead of &mut A, we could have something like &mut A {name}, which is a mutable reference to Foo that is only permitted access to the name field. Then you could write something like this:
struct A {
data: Vec<T>,
name: Option<String>,
};
impl A {
fn set_name(&mut self {name}, name: String) {
if self.name.is_some() { panic!("Help! My name is not supposed to change!"); }
self.name = Some(name);
}
fn read_data(&mut self) { .. }
}
Now the compiler could consider the call to name safe, since self.name is not borrowed. I guess one could imagine even encoding that borrow into the signature, sort of like:
fn set_name(self: &mut A { name: ref mut name, .. }, ..)
Or of course we could maybe move the annotation to the fn signature, sort of like a where clause:
fn set_name(&mut self, name: String) writes self.name { ... }
That’s probably the most realistic, but it’s also probably not how the type system would think of things under the covers.
Anyway, until now, we’ve shied away from these sorts of annotations out of a desire to keep the complexity of the overall annotations under control, but of course the cost of that is that one often has to restructure one’s code into distinct types or free functions to avoid these errors.
Now, your first example, with the hash-map, is much harder. For that we’d have to actually know that the keys are unequal, and we’d have to understand the details of how hashmap is implemented. It’d be much more plausible for slices ([T]), since indexing of those is built into the compiler, and the keys are integers, but even there it’s pretty hard.