Error: local variable is never mutated

Just a heads up: today’s fresh dev build of Zig 0.12 (0.12.0-dev.1664+8ca4a5240) introduced this requirement where you must declare your var a const if you never modify it.

An example:

const std = @import("std");

pub fn main() void
    var a: u32 = 0;
    std.debug.print("a = {}\n", .{a});

The error:

$ zig run var_not_mutated.zig 
var_not_mutated:5:9: error: local variable is never mutated
    var a: u32 = 0;
var_not_mutated:5:9: note: consider using 'const'

Some may not like it, just as some complain about unused variable / constant being an error, but I think this is a Good Thing™.


I agree with your sentiment. Const should really be the go to unless you plan on changing something. It makes longer code examples easier to read (I read a lot of convoluted error handling code at work and one of the best ways to clean it up is to declare existing stuff as const… immediately tells you what’s actually being changed).


Indeed. I have noticed I do have several example programs in my Zig port of some of raylib examples (shameless plug) that now produce this error as I was testing them with a new dev build of Zig.

I’m happy Zig compiler now points out the places where I could (and now, must) use const instead of var. Thumbs up!

1 Like

It is fantastic that we get this help from the compiler to catch “sloppy” code.

But I also wish I could tell the compiler to let the sloppy code be “for now”. For example, when I am making quick changes and prototyping. I don’t mind being reminded I should clean it up at some point; I wish I could tell the compiler to “leave me alone” for now.

To potential repliers: please don’t cast this into a discussion about this sort of error being bad. I love having these errors. All I am saying is, I wish I could quiet them down while I am exploring my alternatives.


I understand what you’re saying… but I’m fearful that this “for now” switch can become permanent in some projects. You know what they say, there’s nothing more permanent than temporary solutions.


Your point about exploration is really interesting and it got me to thinking more about what this change implies.

It does make new users pay a heavier upfront fee in required learning… ya know, “don’t do x because x won’t compile due to y”. On the whole, I think this is a good change, but too much of it can quickly become constricting and incentivize people to do exploration elsewhere (like writing drafts in python instead of directly using the intended language itself).

There’s a fine line here. Back in ye olden times, programs could just stomp on the memory of another process. Sure, you had more freedom, but that also meant you had all the consequences of that freedom. Some people actually dislike type explicit languages entirely for this reason (it supposedly inhibits them from trying things).


I remember times when I have wanted to explore what I call “stupid changes” in my codebase – turn a whole thing off (which I know will break the algorithms) just to see if the impact on performance, or some other effect, is what I think it is. I usually do this in C with #if 0 / #endif blocks, which nest nicely. The point is, I know this is not the state of the code I want; I just want to see what happens. This is what I referred to as “exploration” above.

With this change (and related features, such as flagging parameters / variables when they are unused), and without the possibility of turning on this “sloppy mode” (temporarily making these errors into warnings, or hiding them), I find that sometimes this type of change percolates into a chain of useless changes, just because I wanted to see “what happens if…”.



If it is a warning, and a simple formatter can fix it, I don’t see how could that happen, or be a problem.

A big and fundamental project refuse to apply the formatter to delete unused variables and to turn the unmutated variables constant? Doesn’t seem likely to me.

Actively not letting people to prototype and refactor and experiment with code is a direct problem however.

Having to change var to const and vice versa periodically, or add / remove _ = my_var; in prototype code you constantly edit anyway… I don’t know, doesn’t look like a big deal IMHO.

Oh no! In coding “for now” is a synonym for “forever”.


I simply disagree. I would like to have this available in my toolchain.


One way of doing this would be adding a flag --sane or --strict that will not give you any room for shenanigans – error out on all unused parameters / variables, on vars that should be consts, etc. Maybe even let this be the default configuration, but have a way to turn it off.

From past discussions, it certainly seems that this will not happen. Oh well.

1 Like

I’m afraid such a flag would be abused. The only mitigation I could think of is for the compiler to ask for a time-limited token, a-la Github personal access tokens, with this flag. But then again people would just write scripts and automate token generation and it’s not going to help at all…

I think we have different definitions / expectations of “abuse”, and how far the tooling should go to “protect from” / disable unwanted behaviour. If zig foo bar errors out on any of the things we have discussed here, how can it be abused? In this case, I am forced to write zig --sloppy foo bar if I want to opt into the sloppy mode.


The thing I am worried about is not somebody having an option to disable guardrails while playing in his/her sandbox, but that this option is going to be (ab)used “in production environment”. E.g. “we need this feature last week, no time to refactor, we’ll do it later”, etc.
And no, administrative measures (“don’t use --sloppy”!) don’t always work.

1 Like

Isn’t that way of abuse already there with --autofix?
Honestly I think autofix is even worse than sloppy in that regard, since at least with sloppy we can get the errors back by removing the flag later.
Also “don’t use --sloppy in production” can be enforced by the project manager with CI, while --autofix usage cannot even be detected reliably.


In early days of Rust (before 1.0 release) there was lot of pressure to make borrow-checker optionally replace errors with warnings. But Rust team resisted and now memory safety guaranteed by borrow-checker is the Rust’s main selling point. Unconditionally barring sloppy code from compiling is a feature not a bug.


I’m unfamiliar with --autofix. What is it?

It can be enforced but it doesn’t necessarily have to be. Like I said, there are circumstances when you will be tempted (or outright pushed by the management) to skip the checks and just ship the next release already.

It automatically inserts _ = and other fixes to compiler errors like this: stage2: --autofix proof-of-concept by andrewrk · Pull Request #12803 · ziglang/zig · GitHub
It wasn’t added to the language (yet?), but zls basically does the same thing.

1 Like

If you absolutely have to have a var that you do not mutate at the moment, there is an officially blessed (by the PR author) escape hatch:

var x: i32 = 0;
_ = &x; // makes compiler happy. `&` promotes x to l-value
1 Like