Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use let keyword instead of const for immutable variable declarations #181

Closed
andrewrk opened this issue Sep 1, 2016 · 60 comments
Closed
Labels
breaking Implementing this issue could cause existing code to no longer compile or have different behavior. proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Milestone

Comments

@andrewrk
Copy link
Member

andrewrk commented Sep 1, 2016

Currently, we have const and var for variable initializations.

const means that the bytes directly referenced by the variable cannot change after this assignment.
var means that they can.

Previously we had copied Rust and had let and let mut. See #34.

@procedural has suggested renaming const to let.

@thejoshwolfe says:

Where's the burden of proof here?
const means it doesn't change, which matches the behavior of a const declaration. let is used in javascript and other languages for any kind of local variable

One argument for let over const is that it's the same length as var and thus as easy to type. In general the language is supposed to guide the programmer into doing the Right Thing, which is to default to using const/let for as many things as possible instead of var.

One argument for const is that it matches the syntax of C, C++, and JavaScript.

In this issue, let's figure out How It Should Be and make a decision. Then once the decision is made we can refer people to this issue.

@andrewrk andrewrk added this to the 0.1.0 milestone Sep 1, 2016
@andrewrk
Copy link
Member Author

andrewrk commented Sep 1, 2016

Also relevant is #83. "...if we had this different return value syntax, it implies that we should support tuples..."

If we had tuples, then we may consider following Rust's lead and having let and let mut so we can do things like let (mut a, b) = foo().

@ghost
Copy link

ghost commented Sep 1, 2016

Can we support both? Like, let x = ... will be the same as const x = ...? Because I don't want to see [] const u8 string be written like [] let u8, but I also don't want to type const x = ... for every immutable variable I declare (in Swift, I used let everywhere, so typing it does matter, at least for me)

@ghost
Copy link

ghost commented Sep 1, 2016

I think removing const will make at least some people angry, so ideally we need both, let as a shortcut for const for people who don't want to type it for variable declarations. Zig is is fine having multiple shortcuts for error handling anyway, so why not add just one that saves typing for the most common operation. And if not adding let at all, well, there's 33K of people who liked Swift on Github, more than Rust or Go, some of them may consider Zig at some point, providing them a familiar syntax can make them happier.

@andrewrk
Copy link
Member Author

andrewrk commented Sep 1, 2016

Having two ways to do the same thing goes against the principle of One Way To Do Things, which exists to support the goal of readability. Uniform code is more readable.

It's OK to have both the let and const keywords exist, but there would not be two ways to do a variable declaration. For example one possibility is that every variable declaration must be let x = ... but const is still used for type qualifiers like []const u8.

Zig is is fine having multiple shortcuts for error handling anyway

Not quite. I'm considering removing the %return ... operator in favor of requiring ... %% |err| return err for just this reason. But, even if zig ends up keeping %return ..., then it will be idiomatic to use %return when you can rather than the longer ... %% |err| return err form.

33K of people who liked Swift on Github, more than Rust or Go, some of them may consider Zig at some point, providing them a familiar syntax can make them happier.

Both Swift and Rust use let and I agree that is an argument in favor of let over const.

@ghost
Copy link

ghost commented Sep 1, 2016

For example one possibility is that every variable declaration must be let x = ... but const is still used for type qualifiers like []const u8.

That would actually be perfect imo!

@phase
Copy link
Contributor

phase commented Sep 2, 2016

From an ML perspective, let is used as a binding between a name and a value, and is usually constant. OCaml uses let x = ref 1 for mutability, which isn't really changing the value underneath. It's reference to a value. Using var and let together could be nice, or you could just make everything immutable.

For tuples, do we really want to allow assigning mutable and immutable data in the same statement? That seems like a little much.

// This is too busy.
let (mut a, b) = foo()

// These are clean.
let (a, b) = foo()
var (c, d) = bar()

I don't like two keywords for assigning a variable. var is short for variable, or vary. The value of variable may change. let is a declaration, stating that this will never change (unless you allow variable shadowing).

@felipecrv
Copy link

JavaScript now has let (local scope), var (function scope) and const (local scope const). It is a mess.

You most likely want to use const most of the time, it is the hardest to type and it adds too much noise. let is not used for defining constants in every language, so it is not the least confusing keyword.

I suggest zig to copy Scala on this: use val and var.

@ghost
Copy link

ghost commented Sep 3, 2016

use val and var

The letters are too visually similar... One last character determines the whole chain of surprising consequences...

let is used as a constant variable declaration by two of the most popular statically typed and compiled languages known today: Rust and Swift. When people get burned by their slowness they'll look for an alternative which will be a language close to the compile speed of C. Zig is the best alternative so far, and it's better be familiar to them in general cases like variable declaration, in my opinion...

@ghost
Copy link

ghost commented Sep 4, 2016

I've updated the patch, trivially made an exception for [] const and & const qualifiers. To try it out:

git clone https://github.com/andrewrk/zig Zig
cd Zig
git checkout f18e34c
cd ..
wget https://gist.githubusercontent.com/procedural/9443f1a5353e158a4422ecc397a96740/raw -O zig_const_to_let.patch
cd Zig
patch -Np1 < ../zig_const_to_let.patch
mkdir build
cd build
CC=clang CXX=clang++ cmake .. -DCMAKE_INSTALL_PREFIX=$(pwd) -DZIG_LIBC_LIB_DIR=$(dirname $(cc -print-file-name=crt1.o)) -DZIG_LIBC_INCLUDE_DIR=$(echo -n | cc -E -x c - -v 2>&1 | grep -B1 "End of search list." | head -n1 | cut -c 2-) -DZIG_LIBC_STATIC_LIB_DIR=$(dirname $(cc -print-file-name=crtbegin.o))
make && make install && cd lib/zig/ && find -type f -print0 | xargs -0 sed -i s_"const"_"let"_g && cd ../..

@andrewrk
Copy link
Member Author

andrewrk commented Sep 5, 2016

Let's make a language table for how to declare a single assignment variable.

  Language | Syntax for single assignment
  ---------+-----------------------------
 C, C++, D | const
      Rust | let
     Swift | let
     OCaml | let
JavaScript | const
     Scala | val
      Java | final
       Nim | let
        Go | = / :=
    Pascal | = / :=

Any others we should add?

@phase
Copy link
Contributor

phase commented Sep 6, 2016

Implicit declaration is an option.

a = 7;

And we could use a keyword for immutables.

const a = 7;

@ghost
Copy link

ghost commented Sep 6, 2016

a = 7;

Now it's no different than assignment, isn't it? :)

@andrewrk
Copy link
Member Author

andrewrk commented Sep 6, 2016

I veto implicit declaration. First of all if there were implicit declaration the default should be immutable, not mutable. And then as far as parsing goes, it's easier to parse for both humans and computers if there is a keyword to indicate that a constant is about to be declared.

And then importantly, in this situation:

var a: i32 = 0;
fn f() {
    const a = 7;
}

We get a compile error saying that we shadowed a. With implicit declaration we would accidentally override the value a with 7.

@felipecrv
Copy link

felipecrv commented Sep 8, 2016

let is probably OK but I have a pet peeve with it: it's a verb. You can say "this is a value and that is a variable", "this is a const and that is a non-const". "this is a let" sounds weird. I think that's why Java and Scala decided to go with final and val.

@xyproto
Copy link

xyproto commented Sep 14, 2016

There is also the option of using different operators for immutable/mutable declaration/assignment.

For example:

Immutable declaration (the most common/most desirable case):

x = 42

Mutable declaration (more cumbersome to type, to discourage heavy use):

x @= 42

Assignment (overwriting the value, more "special" than regular immutable declaration):

x := 42

Go uses := for mutable declaration. Pascal uses := for assignment.

@ghost
Copy link

ghost commented Sep 14, 2016

@xyproto This is not what C programmers would expect... I wouldn't mind getting rid of var / let / const too, but more like in the way Jai does it: x := 42 as both declaration and assignment of an immutable value (since : means in all current languages "is a type of"), x = 42 is your usual C-like assignment and maybe x @= 42 as declaration and assignment of a mutable value...

But I feel like discussions like these deserve another topic (hey, there's also fn for "function" declarations, which are not really functions in the mathematical sense, etc.), I feel like this issue is focused on const vs let more...

@phase
Copy link
Contributor

phase commented Sep 15, 2016

One thing I'd like to add is that let and var are both three letters, which looks nice stacked up.

let a = 7;
let b = 6;
var c = 3;
var d = 7;
let e = 5 + 4;

This wouldn't make a difference if we used Nim's multi-line-variable-declaration.

var
  x, y: int
  a, b, c: string

const
  x = 1
  y = 2
  z = y + 5

@andrewrk
Copy link
Member Author

One thing I'd like to add is that let and var are both three letters, which looks nice stacked up.

Playing devil's advocate here, maybe not stacking them up is better for readability.

Now the sneaky var in the middle of all the const is obvious because of the misalignment.

@andrewrk
Copy link
Member Author

One thing to think about:

const or let in this context means "you may not change the value through this variable".

We also use const for pointers, for example &const. Here it has the same meaning - not that the value pointed to cannot change, but that you cannot change the value through this pointer.

If we used let for variables and const for annotating pointers (&const) then let and const would have the same meaning, but would be different keywords. It seems like a smell. Seems like the same keyword should be used to have the same meaning. Sadly, &let looks pretty weird.

Flipping things around, there is the Rust approach, where things are constant by default, and you have to explicitly declare mutability. In this case you could do let for assigning, & for constant pointer, and &mut for writeable pointer. And maybe mut x = 3; instead of var x = 3;. Or full Rust and let mut x = 3;

@ghost
Copy link

ghost commented Sep 22, 2016

@andrewrk how about mut and let and all &u8 are constant by default? Mutable pointers will be &mut u8

In this case you could do let for assigning, & for constant pointer, and &mut for writeable pointer.

That's what I thought too...

@andrewrk andrewrk added enhancement Solving this issue will likely involve adding new logic or components to the codebase. and removed question labels Jan 24, 2017
@andrewrk
Copy link
Member Author

Current plan:

  • for types, const by default. So &u8 means "constant pointer to a u8" and []u8 means "constant pointer to a u8 with a length"
  • use mut to mean mutable. So &mut u8 means "mutable pointer to a u8" and []mut u8 means "mutable pointer to a u8 with a length"
  • for variables, use let instead of const, and mut instead of var.
  • when you use the & operator, it gets the mutability of the thing you used it on. So if you declare a variable like this: mut x: i32 = 1234 and then you do &x, then the type is &mut i32. If you declare a variable like this: let x: i32 = 1234 and then you do &x, then the type is &i32.
  • instead of using var as the type for parameters when any type is allowed, you would omit the type entirely, the same as you do for type inference on a variable, e.g. let x = true;

@xyproto
Copy link

xyproto commented Jan 25, 2017

Since []u8 means "constant pointer to a u8 with length", why is not mut []u8 "mutable pointer to a u8 with a length"? I would think that []mut u8 would rather read as "pointer with length mutable to u8", which makes less sense than how I would read mut []u8.

In short: Should not the [] belong together with the type rather than the mut keyword?

@andrewrk
Copy link
Member Author

@thejoshwolfe do you have any thoughts on what @xyproto proposed?

@andrewrk
Copy link
Member Author

I think that & and [] are trying to be equivalent syntax, except one is a pointer and one is a slice. So if &mut is a mutable pointer then []mut should be a mutable slice.

@thejoshwolfe
Copy link
Sponsor Contributor

rather than "mutable pointer to a u8", say "pointer to a mutable u8". now the order makes sense (and it's also the correct description). I think the only reason people instinctively think of the phase "const pointer" is confusion caused by C's terrible syntax and terminology.

I'm nervous about making implicitly typed parameters easier to declare than explicitly typed parameters. then the programmers who think you shouldn't use semicolons in javascript will argue that you shouldn't declare parameter types in zig. is that what we want? do we want haskell-style optional parameter type restrictions or do we want a way to declare any-type parameters? effectively equivalent, but different paths of least resistance.

@andrewrk andrewrk changed the title const vs let use let keyword instead of const for immutable variable declarations Dec 31, 2019
@haze
Copy link
Contributor

haze commented Jan 1, 2020

I don't mind changing const to let but I feel like a simple instruction within the documentation that explains the relevance of constant variables would be just as efficient in guiding the programmer to doing the right thing. I feel like If zig were to change const to let, var would be equally as reachable, and let mut would be a syntax complexity hint to the programmer (like usingnamespace is) towards its mutability.

@cshenton
Copy link
Contributor

cshenton commented Jan 2, 2020

One other area where the noun vs. verb distinction between const and let shows up is when using with pub:

pub const MyType = struct{};    // "A publicly visible constant MyType. equal to ..."
pub let MyType = struct{};    // "A publicly visible... let MyType equal ..."

The latter feels quite awkward to me.

@bheads
Copy link

bheads commented Jan 2, 2020

I wanted to also point out that function params (except pointers) are const by default, which makes sense but is different then mutable by default every where else.

I would like to see const by default, if we could drop const or let on variable definition ( := ) that would be the best.

a := true;
b : u8 = 1;
mut c := 1;

Also how do you make a const pointer to const data?

[]const const u8 ?
const []const u8 ?

If const by default then how do you make a mutable pointer to mutable data?

mut [] mut u8 ?
[]mut u8 mut ?

@hryx
Copy link
Sponsor Contributor

hryx commented Jan 3, 2020

@cshenton I feel. It can be reordered to read "Let [public] [mutable] MyType be a struct..."

let BlackHole = struct{};
let pub WhiteHole = struct{};
let DEFAULT_ANGLE: f64 = 1.02;
let mut PREFERRED_ANGLE = DEFAULT_ANGLE;
let pub rot13 = fn (name: [:0]mut u8) void {};
let pub mut handler: ?fn (reason: [:0]u8) void = null;

This has appeal. A downside is that the whole left column is 3 characters, so you can't scan for mutables as easily as with const/var. (See let/var, but less so). This might be a non-issue with syntax highlighting.

This probably screws a little with the accepted destructuring proposal.

let x, y, mut z, s.field = blk: {
    break :blk 1, 2.2, 'c', &val;
}

@bheads

if we could drop const or let on variable definition ( := ) that would be the best.

Having used := in Go for a long time, I would recommend against that operator for a few reasons. It is easy to mistake for = at a glance, and this has led to a few bugs in my experience (although those may have had something to do with its usage in multiple assignment operations). Additionally, the lack of a declaration keyword makes it harder to spot declarations quickly (Go does have var, but the more concise := disincentivizes it).

@Srekel
Copy link

Srekel commented Jan 6, 2020

Just to add my two cents. I'm personally for const rather than let, but of course it's not a deal breaker.

I've been told a couple of times that "you should use const there where you used var", and it's not like I used var because it's simpler to type. I was just lazy about constness, or didn't quite understand how/where to use const properly. Changing it to let wouldn't have helped.

Const feels more "telling". I feel like the meaning of "let" is only clear if you already know it from some other language, or if you're mathematically inclined perhaps. But there's nothing in particular about the word that conveys the meaning in the same way const does.

That both let and var are three letters, I mean sure, it makes it look a bit more consistent if you have many declarations in a row? Otherwise I feel like it's actually a plus that const looks different - it becomes easier to notice. Useful for code reviews if nothing else.

@bheads
Copy link

bheads commented Jan 6, 2020

My suggestion of := is based on the idea that we already have : type = in the language. I would like to see var only used for the generic type or something better(I think jai does $name though another symbol would be better for non-us keyboards).

@hryx Googling golan short variable declaration problems, the only complaints I found are related to variable shadowing when used in multiple assignment. Which I think is what you might have pointed out.

@frmdstryr
Copy link
Contributor

frmdstryr commented Jan 10, 2020

I don't like this. When first seeing zig, the difference between var and const was clear without needing to lookup anything in the docs. In my opinion let does not in any way imply constant and is more ambiguous.

@fengb
Copy link
Contributor

fengb commented Jan 10, 2020

Half baked idea carried from #4107 (comment)

Swapping between const and var puts mutability first. What if instead we focused on scope level?

  • let or local
  • static or global — I don't really like either since they have different usage implications, but they behave the same
  • comptime — replaces comptime const

Default everything to const and append a separate keyword for mutability, e.g. let mut a la Rust or even local var

Edit: with assigned anon functions being the new norm, I'm not sure I like this idea because all struct methods would have to be static foo = fn(), which looks verbose and deceptive to any existing language.

@MasterQ32
Copy link
Contributor

I think the grammar status quo is quite good, but changing const to let in declarations is a good thing. First, because it's not only shorter to type than const, but also easier (at least for me). But it would benefit in another way:

const would now indicate "immutable access" whereas let would indicate "immutable value":

let foo : i32 = 10; // will never be mutated
var bar : i32 = 20; // will maybe be mutated
var bam : *const i32 = &bar; // the pointer may point to some mutable data, but may not be mutated via the pointer itself

@metaleap
Copy link
Contributor

metaleap commented Jan 12, 2020

My arguments for keeping const and var just as they are:

This issue has been Open for 3.3 years now. That's around 1212 mornings where no project member with the power to do so woke up with a firm decision made in their gut to go "let over const: yeah let's do this, it's the needful & righteous move". So just by that metric it maybe never was such an appealing notion. My few cents as a newcomer very enthusiastic about the current Zig lang that (after a few years of my observation from the fence) seems to have taken great care to adhere to its founding principles and prevent the ever-present danger of "fluff creep" that comes with growing community / adoption: there's really no pain in const. It's actually neat that it's a different shape from var. Propose a tweak to fmt if the alignment aesthetics disrupt your inner zen. Sooner or later every serious Zigger will have auto-complete in their editor to just type co<tab> so 5-vs-3 chars is moot in terms of "time invest".

More crucially, keeping const is the lowest-friction choice for adoption by developers living much of their time in the 2 mainstream "ends of the spectrum": C and JS. Both already established const with now well-known meaning. What Ocaml or Rust or Scala comparatively-niche-arenas use has what bearing exactly? Being immutable-by-default their let is less ambiguous than it would be in any real budding "C alternative" that is still interested in winning over existing C aficionados --- and code-bases! For such transitions, different keywords for same concepts are minor-but-adds-up cognitive ballasts without payoff. Every purely-cosmetic divergence from C here is already "friction" bound to repel a handful prospects here or there, potentially preventing or fatally-delaying "critical mass" for a better-than-C Ziggy future. Nothing "real" is gained by switching to let. A great appeal of Zig is its apparent laser-sharp focus on essentials and simple unambiguous language prims. Why add further to the already sizable set of keywords (as const would still have to remain in the grammar for other uses as was pointed out).

Let-over-const proposal / discussion / possibility arguably also either doesn't serve or somewhat collides to some degrees with these Zens, though this is a subjective take:

  • Communicate intent precisely.
  • Favor reading code over writing code.
  • Only one obvious way to do things.
  • Reduce the amount one must remember.
  • Minimize energy spent on coding style.
  • Together we serve end users.

Not to flamebait or cause endless mudslinging over each others' interpretations of these mantras 😆 just a peaceably-like reminder to not lose sight there

@jayschwa
Copy link
Sponsor Contributor

There is a nice symmetry between const and var that would be lost by switching to let. While I share the opinion that const is a tad long for such a common keyword, I think that would be better addressed by some kind of "const by default" proposal, not renaming the keyword.

@Rocknest
Copy link
Contributor

I think that const and var are recognizable and unambiguous. let may be confusing, especially to programmers who are more familiar with JavaScript.

However if it is desired to have immutable variable declaration shorter than mutable it would make sense to change var to something longer, eg. mutable a = something;

Another alternative is val instead of const, but this is not compatible with var so it would have to change in that case.

@alexpana
Copy link

As far as readability goes, I think const is more explicit than let. I also don't think it's a good idea to use Rust as a (partial) example, since Rust uses let for all its bindings. In Rust, let doesn't mean "const", it means "new binding", which happens to be const by default to encourage correctness. Mutability is added as a modifier to the binding, not an alternative syntax: let mut vs var. If we use Rust as a reference, I suggest using the entire syntax: let and let mut and remove var.

@kavika13
Copy link
Contributor

kavika13 commented Jan 13, 2020

I agree with @metaleap and @Rocknest - maybe leave const alone.

Maybe var is the confusing one. If Zig succeeds in replacing C, it might also find its way into universities and textbooks as a first programming language to learn.

We're really talking about "immutable variables" and "mutable variables" here. I have seen "variables" be confusing to students, especially those who had a good grasp on math, but who didn't understand the imperative programming model. (I am not a teacher, just someone who's done some minor tutoring a dozen or so times before). Naming them const and mutable might alleviate some of that confusion, because they're both new concepts, and also both unambiguous.

But maybe this is getting a bit pedantic. At the least, renaming var -> mutable would solve the "const is longer than var, so people prefer var" problem. var also might seem a bit too "javascripty" to C snobs, so avoiding that keyword might make them recoil less :)

If the var keyword were renamed, then the var parameter type would have to change too. That might also be useful, cause the var parameter type is also a bit confusing right now.

@ghost
Copy link

ghost commented Jan 13, 2020

More crucially, keeping const is the lowest-friction choice for adoption by developers living much of their time in the 2 mainstream "ends of the spectrum": C and JS. Both already established const with now well-known meaning. What Ocaml or Rust or Scala comparatively-niche-arenas use has what bearing exactly? Being immutable-by-default their let is less ambiguous than it would be in any real budding "C alternative" that is still interested in winning over existing C aficionados --- and code-bases! For such transitions, different keywords for same concepts are minor-but-adds-up cognitive ballasts without payoff. Every purely-cosmetic divergence from C here is already "friction" bound to repel a handful prospects here or there, potentially preventing or fatally-delaying "critical mass" for a better-than-C Ziggy future. Nothing "real" is gained by switching to let. A great appeal of Zig is its apparent laser-sharp focus on essentials and simple unambiguous language prims. Why add further to the already sizable set of keywords (as const would still have to remain in the grammar for other uses as was pointed out).

Zig is going to throw recognisability for C-programmers out the window with #1717 so I don't think that's a big concern. @metaleap

@Rocknest
Copy link
Contributor

@kavika13 There is a strange stereotype that var means 'dynamic type' so maybe it's fine as parameter type (not dynamicly-chosen-static-type but close)

@aka-mj
Copy link

aka-mj commented Jan 13, 2020

As a bias F# fan, I'm in favor of dropping const and var and going with let and let mut. I'd prefer to see the default be immutable, so compile time checks could be done to verify you didn't try to modify something immutable. If functions functions could be defined with let as well (like in F#, OCaml, and others) then functions could also be expressions, one of the goals of proposal #1717.

@momumi
Copy link
Contributor

momumi commented Jan 20, 2020

One thing that I don't like about C is that pointers are mutable by default. When you define a function signature in C, you always should prefer const pointers because they tell the user of your API that the data won't get modified. If you look up the function signatures of something like strings.h in C (ie man 3 string), the majority of functions need an explicit const because pointers are mutable by default in C.

Zig partially solves this problem by making function arguments const by default and you don't need to pass pointers as often. However, when you do pass pointers and slices, their data is mutable by default. So a function signature like fn sum(array: []u32) u32 allows an implementation like this:

fn sum(array: []u32) u32 {
    var result: u32 = 0;
    for (array) |*val| {
        result += val.*;
        val.* = 0;
    }
    return result;
}

pub fn main() anyerror!void {
    var numbers = [3]u32 {1,2,3};
    warn("sum: {}\n", .{ sum(numbers[0..]) }); // prints 6
    warn("sum: {}\n", .{ sum(numbers[0..]) }); // prints 0
}

So I think pointer/slice types being mutable by default is bad, since you shouldn't be able to modify data unless you explicitly ask to.

My proposal

So putting this together with #4107, I'd propose this system:

const       foo: usize = 0; // constant
var         bar: usize = 1; // local variable lives as long as current scope
static      baz: usize = 2; // static variable lives as long as the program
threadlocal qux: usize = 3; // threadlocal variable

const x: *usize;        // ptr to const data
const y: *const usize;  // same as above (redundant, probably don't need this?)
const z: *var usize;    // ptr to variable data
const r: []usize;       // slice with constant data
const s: []const usize; // same as above (redundant, probably don't need this?)
const t: []var usize;   // slice with variable data
  1. Restrict var to local variables inside functions and require either const, static or threadlocal for global variables.
  2. use static for mutable global variables instead of overloading var based on context (Make global mutable variables more easily auditable? #4107)
  3. Change threadlocal var into threadlocal since the usage of var is redundant (ie. if you want a threadlocal const variable you'd just use const)
  4. Make pointers and slices immutable by default and require *var and []var for mutable versions.
  5. Allow static and threadlocal inside functions.
  6. Compiler emits warnings if you use a var, static or threadlocal when you should have used a const.

I added point 5 because zig already allows "static" variables inside functions if you wrap them in a struct. That's more tedious than just using global variable outside the function, so people might be tempted to use globals instead. However, a global variable can be mutated anywhere in the file which increases it's scope more than necessary. Just using static/threadlocal inside the function makes the code's intent much more explicit. However, maybe there's good reasons not to allow this?

@kavika13
Copy link
Contributor

kavika13 commented Jan 21, 2020

One thing that I don't like about C is that pointers are mutable by default. When you define a function signature in C, you always should prefer const pointers because they tell the user of your API that the data won't get modified. If you look up the function signatures of something like strings.h in C (ie man 3 string), the majority of functions need an explicit const because pointers are mutable by default in C.

Zig partially solves this problem by making function arguments const by default and you don't need to pass pointers as often. However, when you do pass pointers and slices, their data is mutable by default.

@momumi I can agree with all this. I predict a little friction from people who are used to mutable being the default, but I personally buy into "mutability needs to be auditable, and thus explicit", even in an imperative language.

Also, I think the keyword should be mutable not var. I'm repeating myself cause this proposal (and other replies, not just this one) kinda pretends the rest of the thread doesn't exist :P - #181 (comment)

Edit: I split this into two replies, one regarding mutability, one regarding storage specifiers, so people can react to them separately

@kavika13
Copy link
Contributor

kavika13 commented Jan 21, 2020

static baz: usize = 2; // static variable lives as long as the program

@momumi I think this throws the thread off track. This thread discusses mutable vs immutable, and throwing storage specifiers in here just tosses a wrench in the works.

I don't disagree with being able to properly address storage specifiers, but I am not sure that it should be bundled in with this particular issue. I think it can be discussed separately, and doesn't have to be tied together. I think this is especially true if you don't conflate const/mutable with static/local/global/threadlocal.

In fact, IMO, you could have keywords for all those things, and specifying more than one keyword at a time could make everything less confusing, even if some of the combos are weird. I.e. if const were default, then static should imply static const. It is nonsensical, but the actually useful static mutable IMO is exactly as ugly and exactly as auditable/greppable as it should be. It'd be easier to document and understand. Sure, no one would ever type static or static const, but really IMO static mutable is radioactive anyhow, for similar reasons as global variables (non-idempotent functions). Might as well make the user acknowledge that.

zig already allows "static" variables inside functions if you wrap them in a struct

Yeah, I think we should make that more explicit too. A variable acting "global" inside a struct, without some sort of explicitly auditable syntax for it, I think is a mistake. I think making that more explicit could ease the seeming need for this static storage specifier syntax. That's also potentially getting quite off-topic. IMO, the issue you linked is a better place to discuss that - #4107

Anyhow, I think storage specifiers can and should be discussed separately. I think they shouldn't be conflated with mutability/immutability. Unless people disagree with me, then I think we should stop discussing them on this thread.

Edit: I split this into two replies, one regarding mutability, one regarding storage specifiers, so people can react to them separately

@momumi
Copy link
Contributor

momumi commented Jan 21, 2020

This thread discusses mutable vs immutable, and throwing storage specifiers in here just tosses a wrench in the works.

Yeah, you probably right, I guess what I wanted to bring up is how let and mut would play with storage specifiers. The idea behind this proposal is to use let to mean "declare a variable". So it seems logical to have stuff like:

let                 x: usize = 1; // declare const "variable"
let mut             x: usize = 1; // declare local variable
pub let static mut  x: usize = 1; // declare static mutabable variable that's exported
let threadlocal mut x: usize = 1; // declare threadlocal variable

I think the more keywords you can chain together the harder it becomes to read and write (what order do you use?). It also allows for redudancy: let static and let would basically be equivalent.

@kavika13
Copy link
Contributor

kavika13 commented Jan 24, 2020

@momumi

... The idea behind this proposal is to use let to mean "declare a variable" ...

Similar to what others have said previously in the thread, let seems to be kind of a worthless keyword. Ignoring whatever defaults get chosen for the language, the word itself doesn't semantically imply a storage specifier, whether it's immutable or not, or the scope of that bound name.

I think if, as I said in my comment, there was no let/let mut and instead was just const and mutable, then it would reduce some of this seemingly nonsensical stacking that you mentioned.

It also allows for redudancy: let static and let would basically be equivalent.

I don't think Zig const (what you're calling "let" here, due to the proposal) works exactly the same as C/C++ const. It doesn't imply a storage specifier. It can still be a stack value, evaluated at runtime. I think it would depend on the scope.

I think the more keywords you can chain together the harder it becomes to read and write (what order do you use?).

Even better reason to not use let IMO. It's semantic fluff and in my opinion doesn't pull its weight. Getting rid of it just makes things cleaner:

const x: u32 = 1;
mutable x: u32 = 1;
static x: u32 = 1;  // const implied
static const x: u32 = 1;  // redundant, but explicit
static mutable x: u32 = 1;
pub static mutable x: u32 = 1;

The redundancy in those examples could be a compiler warning, if it seems important. Same with examples like yours above, if [] const u8 and []u8 were made to be the same thing, and you had to type []mutable u8 to make it modifiable. Then the compiler could warn for the now-redundant []const u8, if it made sense to avoid that redundancy.

As for threadlocal, I don't know all the contexts it is valid in (module scope? local scope? struct scope?), so I can't fully comment. However getting rid of let still makes your example nicer:

threadlocal mutable x: u32 = 1;

@momumi
Copy link
Contributor

momumi commented Jan 24, 2020

At the end of the day it should be a compiler error to declare x as a mutable type and not modify it, so it's just syntax bike shedding whether we use var x/let mut x/mutable x.

So if it comes down to preference, I put my vote behind the more concise system because it's quicker to read and still conveys the same information. I don't care that much if we use mutable x instead of var x or whatever, but I do prefer using one word when we don't need to use two.

I have seen "variables" be confusing to students, especially those who had a good grasp on math, but who didn't understand the imperative programming model.

I don't think the use of the word variable is the main problem here, I'd guess it's more the concept of equality. In math when you see x = 1 that's more of a truth statement so it doesn't make sense for x = 2 to also be true. R was made by statisticians, and they use x <- 1 for assignment instead.

const. It doesn't imply a storage specifier. It can still be a stack value, evaluated at runtime. I think it would depend on the scope.

Yeah, const isn't really a storage specifier, I guess my point is I don't see a use case for static const over const. Maybe if you're working on something like an 8051 with strange memory spaces, but then you need to provide some sort of link section specifier anyway which you could just attach to const.

As for threadlocal, I don't know all the contexts it is valid in (module scope? local scope? struct scope?), so I can't fully comment.

Zig already gives an error when you use threadlocal const, so I'm pretty sure there's not valid a use case for it.

With the only use cases being static mutable and threadlocal mutable, I don't see the point in requiring extra boiler plate. To me static and threadlocal are easier to read and just as explicit.

I'm not the best at communicating, so sorry if I'm coming across as argumentative. Anyway have a great day :)

@kavika13
Copy link
Contributor

At the end of the day it should be a compiler error to declare x as a mutable type and not modify it, so it's just syntax bike shedding whether we use var x/let mut x/mutable x.

Yup, I agree on that point. Those are all recognizable mnemonics for mutable assignment.

I think const vs let is less of a bikeshed, because the word let itself only implies assignment and doesn't imply mutable/immutable.

With the only use cases being static mutable and threadlocal mutable, I don't see the point in requiring extra boiler plate. To me static and threadlocal are easier to read and just as explicit.

Yeah, I think that's fine!

I care most about whether I can grep for values that support mutable assignment. If it's mutable or (mutable|static|threadlocal), I don't care. If it's possible to arrange the keywords in such a way that a mechanical grep is a perfect filter (zero false positives or negatives), then I want Zig to do that!

I suspect it is possible and not actually hard to do. We can verify it by someone exhaustively enumerating the possible qualifiers in all their contexts, and seeing if there's any holes.

If the list for that grep is very short, that would also be nice. 3 keywords isn't so bad. 1 would be awesome, but I can sacrifice that if said grep is contractually guaranteed by the language, and well documented :)

If the language is also arranged in a way that keyword soup doesn't have to happen unless I'm doing very questionable and hard to audit things (in which case keyword soup will help with the audit), then that'd also be nice. IMO it takes second priority, though it is worth considering. In this particular case, I don't think the two goals are at odds.

I'm not the best at communicating, so sorry if I'm coming across as argumentative. Anyway have a great day :)

Same to you as well! Hope I'm not being argumentative, hope you have a good day :)

@andrewrk
Copy link
Member Author

Once again after careful consideration I'm landing on status quo.

Thanks everybody for your input and apologies for the design churn.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
breaking Implementing this issue could cause existing code to no longer compile or have different behavior. proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Projects
None yet
Development

No branches or pull requests