Nitpick: "expensive" may be the wrong word there, to me that implies some kind of runtime cost. The stated reason for the keyword is that it forces users to think a little bit more about mutability. You may want do this because safe mutability requires exclusive ownership.
But that is cross-language, which is not ideal but not an internal problem if you work in one code base. (O)Caml also has `let` and is quite a bit older than JS.
C# uses “var” to mean “infer the type”, nothing to do with mutability or not.
“Let” feels like some in-joke, coming from a math via lisp heritage of “let k be any number...” to distinguish it in English from the surrounding writing. I have to guess that in early lisp (= k 5) would be an error as k isn’t defined and (k 5) errors because k isn’t a function and (let (k 5)) comes out of need for a binding function and why not “let”.
In c-like languages “k=5” is an established binding that both programmers and compilers can deal with. What does “let k=5;” add to Rust over “k=5;” ?
From the perspective of "language designers influencing programmers", the difficulty of auto-completing to `let mut` could be seen as something positive.
I think this would have been more true 10 years ago when Haskell and Lisp were the most likely place for people to have encountered "let".
Now? It's in Javascript in its ES6 evolution and related languages, Swift, Rust, and probably other "newer" languages I'm not aware of. Scala had it ten years ago also, but is more mainstream than it was then.
When assigning to val more than once: in a statically checked language the compiler will complain, and devs default to 'val' anyway. In a dynamic language you'd quickly get a runtime error.
Using 'var' instead of 'val' where possible: the compiler/linter will tell you this.
Super clear. The "let" version smells a lot of "I have a CompSci PhD".