Could we get rid of let?

If we look at the construct of a struct:

struct
{
val:i128,//no let here, obviously not needed
}

but somewhere in the code we have to:

let val:i128 = 0;//the : i128 is optional, I've put it here to show similarities

My question is, what are the reasons that we cannot have identical form as in struct declaration:
val:i128 =0;//the : i128 is optional, I've put it here to show similarities

fwiw, this forum is for people using Rust currently, and the internals forum is where language change proposals should be posted.


I think we neither could, nor should try to remove the let keyword.

My reasoning is that having a small amount of redundancy is actually really beneficial for humans. It acts as a visual cue so if I'm looking for where val is declared, I just need to scan my eyes up for a let val. This also lets us disambiguate between when a variable is first defined and when you give it a new value.

Another thing that we learned from C-like languages is that having known prefixes makes searching for things a lot easier. For example, if I wanted to find the definition for foo(), I can find it by searching for "fn foo", whereas if we dropped the fn keyword there'd be no way to find it other than with a smart IDE's "jump to definition" feature.

No longer using the let keyword to introduce a new variable means you can't shadow variables any more. So the following code would no longer compile:

let val = 42;
let val = "Hello, World!";

You also have the small issue that you're now generating huge amounts of code churn and splitting the ecosystem for no tangible gain. Also, any proposal that breaks backwards compatibility is DOA.

11 Likes

Maybe that would make sense when the language was originally created, but it doesn't make sense to do so now.

That is false assumption. It could compile if we wanted to:

val = 42;
//val is i32;
val = "Hello, World!";
//val shadows previous val

Edit: Actually I think you are right.
Third val
val = 3;//what is it new val or assignment to previous val.
So yes, thanks, that clarifies things.

I think that is really beneficial, and actually have the same opinion. Especially with fn. And with let too when I think about it.

1 Like

Yeah, the counter-example also starts to break down and introduce usability issues when once you get to more interesting applications.

For example, imagine something like this:

fn frobnicate<'short, 'long: 'short>(
  values: &'short mut [u32],
) -> &'long mut [u32] { 
  ... 
}

let mut values = vec![1, 2, 3, 4, 5];  // Vec<u32>
let mut values = &mut values[..];  // &'long [u32]
let mut values = frobnicate(values); // &'short [u32]

In the above, it's clear that all three values variables have different lifetimes (owned, long, short), but if we dropped the let we wouldn't know whether the third values = ... is trying to introduce a new variable that shadows the previous (perfectly valid) or if it's trying to update the &'long mut [u32] with a &'short reference (lifetime mismatch error).

It's a good thought experiment, though.

1 Like

There's absolutely a language where you don't need let, python does this, for example. Rust just isn't that language. Let's assume we're talking about a new language with Rust's features, rather than adding this now, just to avoid the also valid compatibility issues.

Firstly, foo = bar; already has a meaning: assign bar to the existing foo. Easy enough to fix: only have it declare a new variable if there isn't one already, right?

Except now declaring a variable somewhere else can now change the meaning of this code without any visible difference. That's bad for being able to understand what some code does. Worse, what if you meant to assign to some variable, but it got renamed, so you're just declaring and then discarding a variable.

You can fix these problems too: use a python style nonlocal foo; to pull in an external foo, or use separate declare and assign ops, say = and :=. But that doesn't seem to be much of an improvement over let anymore. And there's more!

How do you declare mutable variables? mut foo = bar;? What about declaring without assignment? Just foo;? That seems a bit ugly and confusing.

Why should let get this magic, but not fn, mod or any of the other declaration keywords? You could also infer those:

{ args } = std::env;

main() {
  debug = args().includes("--debug");
  if debug {
    debug::enable();
  }
  // ...
}

debug {
  Data { value: String }

  enable() {
   Data { value: "some string" }
    ...
  }
}

Etc., and you could make that work, but you quickly start to see how samey everything looks, and you often have to start encoding guidelines like "types start with a capital, everything else lowercase" as part of the syntax. Is that actually a better language? Certainly there are people who would like that, but it's clear it's a different language to Rust.

1 Like

I think python is a bad example here as python and rust are completely different. Also in python (I'm not sure as I both hate it and don't even try to familiarize myself with it), you even cannot specify the type of variable, can you.
C/C++ and rust is a good comparison.

Anyway, thanks for your reply.
My mind is at peace now. :slight_smile:
Thanks.

That's kind of the point, yes :smile:. But there's technically no reason you couldn't make a Rust like language with a Python like syntax.

1 Like

Well, without let you enter Golang wastelands...

// Create a new variable, with the error of "no new variables on
// LHS of :=" when attempting to shadow an existing one
a := 1

// Assign new value to an already existing variable
a = 1

Do you mean Golang or "Go land"?

1 Like

Golang. Good catch. I use GoLand often, hence muscle memory kicked in.