Hacker News new | past | comments | ask | show | jobs | submit login
On removing let and let mut (verdagon.dev)
33 points by verdagon on April 20, 2022 | hide | past | favorite | 20 comments



Minor correction:

final variables in Java can and do change value. They start default initialized, then are assigned at most once. This is a collosal pain in the neck for people writing multithreaded code.

You can also observe the partially initialized final change value in single threaded code by invoking partially initialized stuff around during initialization.


Incidentally, I'm not personally very fond of "assigning an if-expression to a variable" since it disconnects the variable name from the value it gets. An example with two variables illustrates the point:

  let (value, weight) =             let (value, weight)
    if values.empty() {             if values.empty() {
      (0, 1.0)                        value = 0
    } else {                          weight = 1.0
      (values[0], 1/values.len())   } else {
    }                                 value = values[0]
                                      weight = 1/values.len()
                                    }
I think the right (the "old way") is more readable. However it requires the variables be mutable or declaration be separable from initialization.


That seems like more of an issue with tuples than with expressive if/else. Tuples are known for having this easy-to-miss disconnection (take for example a function call that returns a tuple, which couldn't be avoided like the above situation can)


I agree. I feels like this naming disconnect would not exist in this case if you would work with a record/struct. That is what you sacrefice with tuples.


    let { value, weight } =
        if values.empty() {
            { value: 0, weight: 1.0 }
        } else {
            { value: values[0], weight: 1 / values.len() }
        }
Using JS style object expansion, instead of tuple expansion.


You would use a struct here to initialize two variables? The disconnect already exists, multiple variables or many else-if branches just accentuates it.


While in Rust this is only a cosmetic difference I still prefer the first approach as there is less to read to understand what is happening. Also once you are familiar with the language construct of if expression it is even faster to connect what goes where. When looking at the thing on the right I had to carefully go through each expression to distinguish which tokens are just fillers and which contain relevant information as you cannot be sure the general if behaves in the way you would think it would in this case.


Huh! I think you're right. I never realized that the old way was clearer.

I like your second example there. The compiler could probably very easily ensure that a variable was initialized before first usage.

Thank you for the idea and insight!


> To our great surprise, we've found that our codebases have a lot more declarations than assignments, so it makes sense to require the extra keyword on assignments because they're rarer.

I actually love this statistical approach to language design, draws parallels to something I read that CPU designers make optimisations to the innerworkings their instructionset based on heuristics such as average number of functions arguments etc.


Does Vale’s parser tolerate syntax errors well?

Keywords are great because they provide redundancy in the grammar that allows for good syntax error messages and recovery.

I recently lamented the absence of a let token in Ruby while working on making Sorbet’s parser more tolerant of syntax errors. Consider this Ruby program:

    x.
    y = 0
It doesn’t have a syntax error. Ruby treats this as if the user had written

    x.y=(0)
But because of how the line is broken, probably the user meant to have two separate statements, and is in the middle of inserting a new call like

    x.foo()
    y = 1
Or consider another:

    class
    y = 1
The syntax error Ruby reports here is “class names must start with a capital letter.” It then fails to produce any parse result because there is no end keyword to match the class, throwing away the (properly parsing) `x = 1` subexpression in the parse tree.

In these cases it’s frequently possible to hack around the issues with weird contortions. But in basically every such case, if Ruby had used an explicit let keyword it would have been much easier to for the parser to both report syntax errors in places that might otherwise be ambiguous as well as produce some parse tree for the ones that don’t parse.


A great point, I agree that extra syntax can help with parser errors.

Vale happens to require semicolons at the end of every line, which helps with this (and a few other corner cases around custom binary operators).


I once removed all semicolons from a JS codebase when they became optional and encountered situations where this lead to errors as the same code was parsed differently afterwards.

So yeah, long story short I'm also a proponent of semicolons. They help avoid ambiguity for both humans and machines, and make splitting lines less awkward.


Probably a good choice to not go with a! = 0 which could easily be confused with a != 0 (which would of course throw a compiler error on ‘a’ used but not defined).

That being said I’ve never had a problem with python’s variable definition and assignment syntax being the same where TFA claims it was a failure. Honestly, I even support the walrus operator for the specific reason it was introduced — heresy, I know…


Good point, either a prefix ! or a different letter say could be used.

I think that it would be interesting to do like C# does: use this 'mutable letter' to indicate 'in-out' function parameters.


I'd be very worried about accidental shadowing.

    x = 4
    if (cond) {
        x = 8
        log("x is now: ", x)
    }
    process(x)
Looks right, but it's wrong.


Fortunately, the Vale compiler doesn't allow shadowing. It also says "Did you forget the set keyword?" in case they meant to modify x.


I'm surprised: in Rust they allow variable shadowing because otherwise with the 'immutable by default' design they thought that you'd have to create too many names.

Isn't it an issue for Vale?


The thesis for rust is something like "C++ level performance without the pain". The Zig thesis might be "low level as C without the pain".

What is Vale's thesis?


I'd say Vale's thesis is "Rust level performance without the pain." By making our borrow checker a lot more flexible with shared mutability, we're able to lower the learning curve and architectural restrictions significantly. This could make it a better solution for servers and games, and opens the doors to things like GUI and apps, where Rust could never really fit well.

Though, it's a lot more than that, Vale's also doing some pretty crazy things with concurrency [0], determinism, higher raii [1], and region isolation, all of which will help with preventing and detecting bugs.

[0] https://verdagon.dev/blog/seamless-fearless-structured-concu...

[1] https://verdagon.dev/blog/higher-raii-7drl


See also Prolog and Erlang where assignment is both an assertion test and an initial binding where no binding is there to assert.




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

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

Search: