I agree this code is illegal, but that’s only tangentially related to the missing mut. The code is illegal because it is writing through a pointer obtained from &T without there being an UnsafeCell. miri should eventually be able to enforce immutability of &T, and when it does, it will error on the above code appropriately.
I’m against adding some ad-hoc thing that errors on the code above, but does not also error in the equally illegal
let mut foo = 42;
unsafe { *(&foo as *const i32 as *mut i32) } = 3; // no miri error
assert_eq!(foo, 3);
which still writes through a &T.
So, in terms of UB, I still think mut is just a lint, unlike &mut. Also, mut killed renaming &mut to &uniq, which is a shame.
But at the same time I acknowledge that mut is an extremely useful lint, so this may totally be worth it. I certainly feel the urge myself sometimes to refactor my code to avoid a let mut, and it often makes it prettier; I also look at code with let mut differently (“careful here, something imperative is going on”) and the mut really helps focusing.
So I’m pretty okay with mut as it is, but still think it should have no affect whatsoever on the actual program behavior, on whether a program is UB, or on the unsafe code guidelines. That’s what I mean by “just a lint”.