I definitely agree with const-by-default pointers.
I honestly think that accidentally making a function argument or strict field be a []u8 rather than an []const u8 has got to be a mistake I made in at least 80% of situations where I wanted to pass a string somewhere. Made all the worse that it doesn’t get caught by the compiler until you try to pass it a const slice which should be fine, since it never actually gets modified.
Something that wouldn’t be an issue if the slice defaulted to const and we forgot a var, because the compiler would error out as soon as we tried to modify its contents.
Genuinely I can’t think of any other place in Zig where the language just lets you silently make an error like that that could be caught with a change to the syntax.
i’m not a mind-reader, but i’d bet the solution that Zig will take here is to continue to annoy people by making the “you said var but then didn’t mutate it” compile error more powerful.
That would hurt generic code quite a bit, but I think that’s a separate issue. It could be done under the current syntax too.
That’s good, +1 for incremental change!
My addition here is that those of us that want it could maybe stop at this level with a trivial new linter check. I can make sure I always specify []var u8 or []const u8, and your code doesn’t need to change.
I disagree that the pointee’s mutability should necessarily be communicated by the name of a variable/parameter/declaration. That’s for the type system to do, and it’s good this way.
With that said, this kind of mistake isn’t common for someone familiar with pointers, sure.
But I’ve had my fair share of trouble during my first encounter with pointers, learning C. Rust, with its immutability by default, was nicer for me to learn in this regard.
I don’t disagree that their is utility in having immutability by default, both camps have pros and cons. I am not strongly married to an argument against it either, I am pushing back on a lack of technical arguments in its favor. This like a vim vs emacs argument: there is no actual correct answer (though Vim is better
) It is by-and-large a style choice much more so than a practical one, and the examples thus far fall flat IMHO.
i don’t deny that it is logically possible this could and likely would make a bug easier to find early in some niche situations, I am saying that at this stage of the language development, and it already having weighed this option and opted for mutability, the change is simply not worth it.
Arguments in favor would be much stronger if this was a common issue that we have all been plagued by, even uncommonly, or could be demonstrated using real-world coding patterns we use everyday that would be empirically better with it. As it is, it is a monumental change to all code currently written and inversion of the current mental model, despite whatever magic zig fmt or zls might be able to assist with.
So we have with this:
Pros:
*Tis easier on the eye now that PRO is gone and more*const Tis neededconst a: []u8gives a compile error ifaelements are mutatedmore constness could enable more compiler optimizationsapparently not: Immutability by default - #86 by gwenzek
Cons:
- more verbose for the case you want a mutable slice (
const a: []var u8) - mental shift
- would break all zig code in existence
Anything else?
I definitely do NOT agree that this is simply a choice of taste. My suspicion is that this makes several things in the compiler easier. In particular, I would bet that this makes stack escape analysis DRAMATICALLY easier. Everything being const and deriving from const makes provenance a lot more straightforward. However, you would have to go implement it to see.
And that is NOT a trivial amount of work, though. It would be an INTERESTING piece of work though. It probably also dovetails with something like ityonemo’s CLR:
I don’t agree with “at this stage of the language development”: I think we’re not so close to 1.0 that it needs to be locked down. If the proposed change is better (I think it is, enough to discuss it but not enough to like fight aggressively over it, and certainly not enough to go bother them in official channels), then this shouldn’t be the only reason it doesn’t happen.
On the other hand, I think “having weighed this option” is a strong argument. Really, I think every time I’ve read through his reasoning I’ve agreed with him, even on some rather unpopular bits.
In this case though, without having seen him directly address this, I think there is a nonzero chance that in the early days something like “that is what C did” would have once been the right starting point, and if that’s all it is then I think it’d be worth his & the team’s time to reconsider it today.
(Sorry to be all pedantic, but if there was a change to be made there’s a lot of pedantic-ness needed to think through it. Yours is a well reasoned argument even if I’m picking at the edges.)
Indeed, it’s possible that tons of code that currently has const x: []T never even mutates the elements anyway! Making the elements const (as default) won’t require any changes in that code. But, if somebody replaced every instance of []T with [] var T blindly, it would work, but potentially it would make a number of their arrays needlessly mutable-elemented. I think porting could take a little thought for some code that wasn’t intentionally careful about this already. Of course, changing all instances of [] const T to []T is safe and easy, if one wants the less verbose variant after such a change to default-const.
function parameters are already constant right? you wouldn’t have to do (const a: []var u8) for a mutable slice, just (a: []var u8)
That wouldn’t be in parameters, it would be in local constants. And for parameters var would probably be needed if you intend to mutate the elements.
the brackets around it confused me. my bad
ok so, i think declaring mutable values/pointers/slices should be more verbose than declaring immutable counterparts. like, you’re enabling write option, shouldn’t that be explicit?
people seem to have gotten used to rust’s style of immutable by default approach so mental shift won’t be that big of an issue after getting used to it. though i wish there’s a way to try it out and see if it works (i guess a transpiler should do the trick
).
breaking exsisting code is a serious issue but that’s already happening with each new release of zig.
pros seem to out-weigh the cons and there could be some things i’m not considering. if the change is worth it, they should change it before 1.0 and not regret afterwards
I’m not sure I understand how this change anything for this.
If I’m not mistaken this analysis has to be done on AIR, but the change would only concern the parser.
I have not stated this, I said that it is predominately a style of preference, and that actual technical arguments to justify it are lacking. Hypothetical situations that are practically nonexistent in the real-world. I might even settle for personal anecdotes of a an actual time this would have been a life-saver, but have not even seen that.
This seems like it could be a reasonable assumption, and is at least pointing towards something beneficial that could make me lean the other way, though we would have to test to see what benefits the compiler would actually be able to derive.
I will reiterate again: I am not ideologically against such a proposal, I just see many who are firmly in the affirmative position, but very few who are actually providing justification for their reasoning beyond that they like it.
It’s not really a pro per se, more a mitigation of the cons, like @gert-cuykens said this change is easy to introduce incrementally:
-
Allow
*var Tinstead of*T(breaks nothing), -
Require
*var Tinstead of*T(breaks pointers-to-var prior to 1., can be fully automated), -
Allow
*Tinstead of*const T(breaks nothing from 2.), -
Require
*Tinstead of*const T(breaks pointers-to-const prior to 3., can be fully automated).
Of course, no need to do each step separately. I’d be curious to know though what people think of step 2. Because then we’d have mandatory *var T and mandatory *const T, which hurts generic code, but would it help transitioning the “mental model” of pointers?
if as you say 3 would break nothing, there’s no reason to think 2 and 3 as separate so at no point *const T would be mandatory. And 4 would go with them as well because it’s how things work with comptime right now (you can get the compile error comptime is redundant in this scope). So it would be basically 2 steps. I imagine if they’ll ever do this big breaking change, they’ll break it in one single step (at least your 2-4), they won’t want to break all code more than once for the same reason.
Of course any combination is possible, not necessarily desirable.
The only ways to break only once is 1 then 2&3&4 at once, or 1&2&3&4 in one go.
I see quite a few yea sayers in this thread and just wanted to balance that with a nay. No argument has been presented except: “it would have let me avoid a surprise”. Const by default would also produce surprises.
How would this affect the syntax for pointer captures?
Some examples of pointer captures from the language reference:
for (&some_integers, 0..) |*item, i| {
item.* = @intCast(i);
}
var c = ComplexType{ .ok = 42 }
switch (c) {
ComplexTypeTag.ok => |*value| value.* += 1,
ComplexTypeTag.not_ok => unreachable,
}
var d: anyerror!?u32 = 3;
if (d) |*optional_value| {
if (optional_value.*) |*value| {
value.* = 9;
}
} else |_| {
unreachable;
}