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?
constmeans 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.
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().
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)
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.
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.
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!
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).
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.
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...
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 ../..
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?
Implicit declaration is an option.
a = 7;
And we could use a keyword for immutables.
const a = 7;
a = 7;
Now it's no different than assignment, isn't it? :)
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.
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.
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.
@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...
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
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.
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;
@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...
Current plan:
&u8 means "constant pointer to a u8" and []u8 means "constant pointer to a u8 with a length"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"let instead of const, and mut instead of var.& 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.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;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?
@thejoshwolfe do you have any thoughts on what @xyproto proposed?
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.
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.
is that what we want?
Good point. An alternative to the last point in the current plan is to have a keyword like autotype that you would use in place of an actual type, which is what var does now.
I decided to leave it as const and var, and leave mutable as the default for types, such as &u8. However this item from the previous plan is now implemented and done:
& operator, it gets the mutability of the thing you used it on. So if you declare a variable like this: var x: i32 = 1234 and then you do &x, then the type is &i32 (mutable). If you declare a variable like this: const x: i32 = 1234 and then you do &x, then the type is &const i32 (immutable).What is the reasoning behind the decision not to choose immutability by default?
I find it works quite well in Rust and const-correctness is hard to achieve in C++ because things are mutable by default.
Thanks for your answer.
@antoyo As far as I know, there is no default. There is a choice. var for mutability, const for immutability. There has been discussion that declaring something that is never mutated var should be a compiler error, but I don't know what the option on that was.
EDIT: I guess pointers are mutable by default. Is this what mean?
It's planned to have a compile error for using var and then never mutating the value. That makes immutability the default. The difference between rust and zig in this regard is that the address-of operator does not have a mutability modifier, and instead retains the same mutability as the data that one is taking the address of. I believe that rust's way of changing the mutability when taking an address of something stems from the borrow checker having very different rules for const borrows vs mutable borrows, but zig does not have this restriction. So it makes more sense for address-of to retain the same mutability.
I was actually talking about pointer types.
&u8 is mutable, right?
Thanks.
&u8is mutable, right?
correct
So, what is the reasoning of having &u8 default to mutable?
It would be nice to default to const and have &mut u8 for the mutable alternative.
At the very least, we should have a warning when &const should be used when & alone is used.
Re-opening in light of #1717
Some more data since this was closed 2 years ago:
var for local variables when they should be using const.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.
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.
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 ?
@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).
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.
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.
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.
Half baked idea carried from https://github.com/ziglang/zig/issues/4107#issuecomment-572863512
Swapping between const and var puts mutability first. What if instead we focused on scope level?
let or localstatic or global — I don't really like either since they have different usage implications, but they behave the samecomptime — replaces comptime constDefault 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.
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
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:
Not to flamebait or cause endless mudslinging over each others' interpretations of these mantras :laughing: just a peaceably-like reminder to not lose sight there
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.
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.
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.
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.
More crucially, keeping
constis 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 establishedconstwith now well-known meaning. What Ocaml or Rust or Scala comparatively-niche-arenas use has what bearing exactly? Being immutable-by-default theirletis 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 tolet. 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 https://github.com/ziglang/zig/issues/1717 so I don't think that's a big concern. @metaleap
@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)
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.
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.
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
var to local variables inside functions and require either const, static or threadlocal for global variables.static for mutable global variables instead of overloading var based on context (#4107)threadlocal var into threadlocal since the usage of var is redundant (ie. if you want a threadlocal const variable you'd just use const)*var and []var for mutable versions.static and threadlocal inside functions.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?
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.hin C (ieman 3 string), the majority of functions need an explicitconstbecause pointers are mutable by default in C.Zig partially solves this problem by making function arguments
constby 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 - https://github.com/ziglang/zig/issues/181#issuecomment-573518770
Edit: I split this into two replies, one regarding mutability, one regarding storage specifiers, so people can react to them separately
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 - https://github.com/ziglang/zig/issues/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
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.
@momumi
... The idea behind this proposal is to use
letto 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 staticandletwould 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;
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 :)
At the end of the day it should be a compiler error to declare
xas a mutable type and not modify it, so it's just syntax bike shedding whether we usevar 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 mutableandthreadlocal mutable, I don't see the point in requiring extra boiler plate. To mestaticandthreadlocalare 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 :)
Once again after careful consideration I'm landing on status quo.
Thanks everybody for your input and apologies for the design churn.
Most helpful comment
Once again after careful consideration I'm landing on status quo.
Thanks everybody for your input and apologies for the design churn.