Hacker News new | past | comments | ask | show | jobs | submit login

I'm personally a fan of the Rust way, i.e. `let` for const bindings and `let mut` for mutable ones. I think it's fairly clear which is which, and having to type four extra characters to get mutability helps reinforce the notion of const being the default choice.



I agree with making mutability require more typing, although I'd prefer going without the redundant `let`.


It's not redundant; for example

  let (mut x, y) = (1, 2);
x is mutable, y isn't.

That is, let is the way that you introduce a new binding, always.


Am I being a complete noob that only knows Python (I am) if I ask: Why do you need to declare that something is a mut or string or whatever? Python doesn't seem to need such extra lines with obvious declarations.


A couple important benefits (there are many others, as well as drawbacks) are

1) the compiler can then warn you when you violate your own declarations before the code is ever run. e.g. it can tell you that you've mutated something you said you didn't want to, or that you've taken the sum of an int and a list.

2) the compiler can guarantee certain things at compile time and thus eliminate the overhead of checking them at runtime. since a value in Python can be anything, before performing a string operation on a string the python runtime must first check that it is a string, while the runtime of a language like rust can assume that it is because that was guaranteed at compile time.


As others have mentioned, the idea is to have the compiler enforce certain things for you to reduce the chances of making an error. One illustrative way I've seen it explained is that if you accidentally make a variable immutable when you want it to be mutable, you get a very straightforward compiler error message saying something like "you can't mutate this variable since it's const; maybe you should make it mutable?". On the other hand, if you want a variable to be immutable but it's actually mutable and you change it, you can end up with all sorts of tricky bugs like race conditions which are much harder to debug.


Dynamic languages can convert types in runtime (so 1 + "f" = "1f") and not so many languages (not only dynamic) cares about mutability.


Supporting an addition operator that accepts mixed type operands has no relationship to being dynamically typed.


From quick check, it looks like Java can do this coercion, and it's not dynamically typed.


This is weak typing not dynamic typing.

1 + f in modern Python results in a ValueError.


Not OP, but why do you need the 'let' at all? Why not x = 7 mut y = 3 ?


Because it's a bit too error-prone:

   mut listOfLeftHandedOralHygienistsInKazakhstan = getThem()
   // several lines later
   listOfLeftHandedOralHygienistsInKazahkstan = getThemAgain()
You thought you were reassigning the mutable variable, but you actually created a different immutable variable.

This can be prevented by having different operators for assignment and re-assignment. Some languages do that: in OCaml / F#, re-assignments use '<-' while declarations use 'let (mutable) x =' (they can't drop the 'let' because they use '=' as the Boolean equality operator).


... and whoops, the next thing you know your left handed oral hygienist has apocryphally crashed into Venus.

https://en.wikipedia.org/wiki/Mariner_1


My sibling is correct, but also, there are grammar issues with doing it that way. They're not insurmountable, but much, much simpler with the let.


Sure, but Kotlin is more about being explicit and less about evangelizing one style of programming over another. It's very pragmatic in that way. There are plenty of scenarios where you're maintaining local state (properly encapsulated within a class, exposing a functional interface, etc) and you need mutable fields. Even plenty of scenarios where you want to operate on a value mutably in a function and then return it. It may seem confusing at first but in practice I've never mixed the two up.

But this most likely is because Scala uses val/var.


If your 'let' doesn't propagate so that immutable collections are used, it's not very valuable. Just like 'final' in Java doesn't prevent anyone from mutating your ArrayList. Using 'val' instead of something more suggestive like final/const/'not mut' seems a lot nicer to me. (Edit: and indeed Kotlin having "mutableListOf" and "listOf" separation is a good step in readability. The val/var before either of those is less important.)


That's a misnomer, not a misgiving.

val/var/const/whatever only describe the reference, not the value. It doesn't really make sense for that annotation to, say, swivel a collection between ImmutableList and MutableList.

Now, you might be right that it's confusing, this difference between reference mutability and value mutability. I see beginners struggle with it in Javascript's new let vs const all the time.


Even with Object.freeze() it only prevents mutation of the top-level keys.


> If your 'let' doesn't propagate so that immutable collections are used

It does; if you don't use `let mut`, you can't mutate the variable at all, which includes the contents of a collection.

(Given that someone will mention RefCell if I don't, I'll add: "barring unusual trickery".)


> * If your 'let' doesn't propagate so that immutable collections are used, it's not very valuable.*

That's nonsense.

1. You cannot implement immutable collections without `let` / `final`

2. The Java Memory Model has special visibility guarantees for `final` (which does propagate), making it really, really useful

3. Having the guarantee that a certain reference won't change is still useful even if the object referenced is a mutable ArrayList; e.g. ref = null


1. Sure you can, if you have encapsulation.

2. Java does a lot with the keyword 'final'. I guess here you're talking about the concurrency behaviors of it? Does it bother you that you can remove 'final' via reflection?

3. It's still useful (e.g. not needing to use yoda-style ifs in languages where you can assign inside an if expression Just In Case you forget an =), but not very. That's my whole point.


1. the issue is one of visibility; e.g. you can initiate and pass / share String objects safely between threads without synchronization or worries because underlying String there's a final char[] backing it.

What `final` guarantees is that the variable will be initialized and visible (along with all its referenced objects) before the class constructor is finished. Without this guarantee you'd need `volatile` semantics or locks, which are more problematic.

2. yes, I'm talking about multi-threading; if the user removes "final" via reflection, or modifies final references via reflection, then it gets what he's asking for; but no, it does not bother me because I never do that and I stay away from libraries using reflection anyway.


I thought that's how the majority of languages worked. It just means the variable cannot be changed to reference a different value. It does not make the value immutable.


It really depends on the language. 'const' in C works differently from 'const' in other languages (even C++, which has some great 'const' use cases that make guarantees about whether values will change, not just about the symbol binding), they both work differently than 'final'. Similar confusions with 'static'. If all you want to say is "cannot reassign this symbol", then 'val' is great and sidesteps this whole history of readability and meaning confusion.


In Rust, it does (in general) make the value immutable. Most mutations of values in Rust are done through mutably borrowing the value, which you can't do when the value is defined with `let` rather than `let mut`, the exceptions being things like `Mutex`, which gives you a value that can be mutably borrowed when you acquire the lock.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: